removed examples contrib module
This commit is contained in:
@@ -1,95 +0,0 @@
|
||||
{
|
||||
"extends": "eslint:recommended",
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"globals": {
|
||||
"Drupal": true,
|
||||
"drupalSettings": true,
|
||||
"drupalTranslations": true,
|
||||
"domready": true,
|
||||
"jQuery": true,
|
||||
"_": true,
|
||||
"matchMedia": true,
|
||||
"Backbone": true,
|
||||
"Modernizr": true,
|
||||
"CKEDITOR": true
|
||||
},
|
||||
"rules": {
|
||||
// Errors.
|
||||
"array-bracket-spacing": [2, "never"],
|
||||
"block-scoped-var": 2,
|
||||
"brace-style": [2, "stroustrup", {"allowSingleLine": true}],
|
||||
"comma-dangle": [2, "never"],
|
||||
"comma-spacing": 2,
|
||||
"comma-style": [2, "last"],
|
||||
"computed-property-spacing": [2, "never"],
|
||||
"curly": [2, "all"],
|
||||
"eol-last": 2,
|
||||
"eqeqeq": [2, "smart"],
|
||||
"guard-for-in": 2,
|
||||
"indent": [2, 2, {"SwitchCase": 1}],
|
||||
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
|
||||
"keyword-spacing": [2, {"before": true, "after": true}],
|
||||
"linebreak-style": [2, "unix"],
|
||||
"lines-around-comment": [2, {"beforeBlockComment": true, "afterBlockComment": false}],
|
||||
"new-parens": 2,
|
||||
"no-array-constructor": 2,
|
||||
"no-caller": 2,
|
||||
"no-catch-shadow": 2,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-extra-parens": [2, "functions"],
|
||||
"no-implied-eval": 2,
|
||||
"no-iterator": 2,
|
||||
"no-label-var": 2,
|
||||
"no-labels": 2,
|
||||
"no-lone-blocks": 2,
|
||||
"no-loop-func": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-native-reassign": 2,
|
||||
"no-nested-ternary": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-object": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-process-exit": 2,
|
||||
"no-proto": 2,
|
||||
"no-return-assign": 2,
|
||||
"no-script-url": 2,
|
||||
"no-sequences": 2,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-undefined": 2,
|
||||
"no-unused-expressions": 2,
|
||||
"no-unused-vars": [2, {"vars": "all", "args": "none"}],
|
||||
"no-with": 2,
|
||||
"object-curly-spacing": [2, "never"],
|
||||
"one-var": [2, "never"],
|
||||
"quote-props": [2, "consistent-as-needed"],
|
||||
"quotes": [2, "single", "avoid-escape"],
|
||||
"semi": [2, "always"],
|
||||
"semi-spacing": [2, {"before": false, "after": true}],
|
||||
"space-before-blocks": [2, "always"],
|
||||
"space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
|
||||
"space-in-parens": [2, "never"],
|
||||
"space-infix-ops": 2,
|
||||
"space-unary-ops": [2, { "words": true, "nonwords": false }],
|
||||
"spaced-comment": [2, "always"],
|
||||
"strict": 2,
|
||||
"yoda": [2, "never"],
|
||||
// Warnings.
|
||||
"max-nested-callbacks": [1, 3],
|
||||
"valid-jsdoc": [1, {
|
||||
"prefer": {
|
||||
"returns": "return",
|
||||
"property": "prop"
|
||||
},
|
||||
"requireReturn": false
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
Drupal Examples For Developers: Contributor's Guide
|
||||
===================================================
|
||||
|
||||
Examples for Developers is a community project.
|
||||
|
||||
If you'd like to participate in Examples development, thank you!
|
||||
|
||||
If you are new to Drupal or open source in general, have no fear. Examples is
|
||||
an easy-going project where you can learn some things about how to work on a
|
||||
collaborative project in a friendly environment.
|
||||
|
||||
|
||||
Policies
|
||||
--------
|
||||
|
||||
Examples follows the Drupal core process as much as possible.
|
||||
|
||||
Contributions thus need to be similar in quality to Drupal core patches.
|
||||
Contributions will need to meet the following minimum standards:
|
||||
|
||||
### Normal Drupal issue process
|
||||
|
||||
Drupal projects use patches related to issues. You should know how to make a
|
||||
patch and an interdiff using git. It's fine to develop on github or
|
||||
what-have-you, but eventually it has to be a patch that can be reviewed in the
|
||||
normal Drupal issue process. See the list of resources for some information on
|
||||
how to do do this.
|
||||
|
||||
Your patch will also need to be reviewed by someone other than yourself. Learn
|
||||
about the review process in the resources section.
|
||||
|
||||
### DrupalCI
|
||||
|
||||
Examples uses the Drupal automated testing system to verify the applicability of
|
||||
patches. See `TESTING.md` for details.
|
||||
|
||||
### Drupal coding standards
|
||||
|
||||
All code in Examples should adhere to the Drupal core coding standards. Examples
|
||||
uses the Drupal Coder project and PHP_CodeSniffer to enforce coding standards.
|
||||
Think of this as another test your code must pass. See `STANDARDS.md` for
|
||||
details.
|
||||
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
### Novice
|
||||
|
||||
Drupal novice contribution guide: https://www.drupal.org/novice
|
||||
|
||||
Drupal contribution guide: https://www.drupal.org/contribute
|
||||
|
||||
What's a patch? https://www.drupal.org/patch
|
||||
|
||||
How to make a patch with git: https://www.drupal.org/node/707484
|
||||
|
||||
### Everyone
|
||||
|
||||
How to review a patch: https://www.drupal.org/patch/review
|
||||
|
||||
See `STANDARDS.md` and `TESTING.md` for information on how to run a coding
|
||||
standards test, and also how to run the tests themselves.
|
||||
@@ -1,339 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -1,79 +0,0 @@
|
||||
Examples for Developers
|
||||
=======================
|
||||
|
||||
Project site: http://drupal.org/project/examples
|
||||
|
||||
Code: https://drupal.org/project/examples/git-instructions
|
||||
|
||||
Issues: https://drupal.org/project/issues/examples
|
||||
|
||||
What Is This?
|
||||
-------------
|
||||
|
||||
This set of modules is intended to provide working examples of Drupal 8's
|
||||
features and APIs. The modules strive to be simple, well documented and
|
||||
modification friendly, in order to help developers quickly learn their inner
|
||||
workings.
|
||||
|
||||
These examples are meant to teach you about code-level development for Drupal
|
||||
8. Some solutions might be better served using a contributed module, so that
|
||||
you don't end up having to re-invent the wheel in PHP. When in doubt, look for
|
||||
an existing contrib project that already does what you want, and contribute to
|
||||
that project.
|
||||
|
||||
|
||||
How To Use The Examples
|
||||
-----------------------
|
||||
|
||||
There are three main ways to interact with the examples in this project:
|
||||
|
||||
1. Enable the modules and use them within Drupal. Not all modules will have
|
||||
obvious things to see within your Drupal installation. For instance, while the
|
||||
Page and Form API examples will display forms, the Database API example does not
|
||||
have much that is visible within Drupal.
|
||||
|
||||
2. Read the code. Much effort has gone into making the example code readable,
|
||||
not only in terms of the code itself, but also the extensive inline comments
|
||||
and documentation blocks.
|
||||
|
||||
3. Browse the code and documentation on the web. There are two main places to
|
||||
do this:
|
||||
|
||||
* https://api.drupal.org/api/examples is the main API site for all of Drupal.
|
||||
It has all manner of cross-linked references between the example code and the
|
||||
APIs being demonstrated. All of the Doxygen-based comments in the code are
|
||||
parsed and made browseable here.
|
||||
|
||||
* http://drupalcode.org/project/examples.git allows you to browse the git
|
||||
repository for the Examples project.
|
||||
|
||||
This project ships with a composer.json file. This is meant to illustrate how
|
||||
to provide a composer.json file for a Drupal contrib project. You can read more
|
||||
about how to use Composer with Drupal here: https://www.drupal.org/node/2718229
|
||||
|
||||
How To Install The Modules
|
||||
--------------------------
|
||||
|
||||
1. The Examples project installs like any other Drupal module. There is extensive
|
||||
documentation on how to do this here:
|
||||
https://drupal.org/documentation/install/modules-themes/modules-8 But essentially:
|
||||
Download the tarball and expand it into the modules/ directory in your Drupal 8
|
||||
installation.
|
||||
|
||||
2. Within Drupal, enable any Example sub-module you wish to explore in Admin
|
||||
menu > Extend.
|
||||
|
||||
3. Rebuild access permissions if you are prompted to.
|
||||
|
||||
4. Profit! The links for Examples material will appear in your Tools menu. This
|
||||
menu appears on the left sidebar by default. You'll need to reenable it if you
|
||||
removed it.
|
||||
|
||||
Having seen the behavior of the various example modules, you can move on to
|
||||
reading the code, experimenting with it, and hopefully grasp how things work.
|
||||
|
||||
If you find a problem, incorrect comment, obsolete or improper code or such,
|
||||
please search for an issue about it at http://drupal.org/project/issues/examples
|
||||
If there isn't already an issue for it, please create a new one.
|
||||
|
||||
Thanks.
|
||||
@@ -1,86 +0,0 @@
|
||||
Developing with Coding Standards for Examples for Developers
|
||||
============================================================
|
||||
|
||||
Examples uses mostly the same coding standards as Drupal core.
|
||||
|
||||
If you see a discrepancy between the coding standards tools used by core and
|
||||
those used by Examples, please file an issue so that Examples can follow core.
|
||||
|
||||
Examples uses the `phpcs` tool to allow for checking PHP coding standards. We
|
||||
use the `drupal/coder` project for Drupal-specific coding standards.
|
||||
|
||||
We also use `eslint` for JavaScript coding standards, and `csslint` for CSS.
|
||||
|
||||
Examples has a `phpcs.xml.dist` file at the root of the project. phpcs uses this
|
||||
file to specify the current coding standards 'sniffs' which code in the project
|
||||
must pass.
|
||||
|
||||
Contributors should install `phpcs` in their local Drupal installation, and then
|
||||
use that to run `phpcs` against Examples as part of their development and review
|
||||
process. (See details below on how to install and run this tool.)
|
||||
|
||||
Contributors can also patch the `phpcs.xml.dist` file itself, in order to fix
|
||||
the codebase to pass a given rule or sniff. Patches which do this should be
|
||||
limited to a single rule or sniff, in order make the patch easier to review.
|
||||
|
||||
Examples also uses the Coder project (`drupal/coder`), which adds additional
|
||||
Drupal-specific coding standards. We're currently locked to Coder version
|
||||
8.2.8, but this should change to reflect the state of core's coding standards.
|
||||
|
||||
Installing phpcs
|
||||
----------------
|
||||
|
||||
Current versions of Drupal 8 core require phpcs and Coder as dev dependencies.
|
||||
That means they're already probably installed in your core vendor/ directory.
|
||||
|
||||
We need to tell `phpcs` to use the Drupal coding standard provided by Coder,
|
||||
because it isn't configured that way by default.
|
||||
|
||||
Like this:
|
||||
|
||||
$ cd my/drupal/root/
|
||||
$ ./vendor/bin/phpcs --config-set installed_paths /full/path/to/drupal/vendor/drupal/coder/coder_sniffer/
|
||||
// phpcs now knows how to find the Drupal standard. You can test it:
|
||||
$ cd core
|
||||
$ ../vendor/bin/phpcs -e --standard=Drupal
|
||||
// Shows you a bunch of Drupal-related sniffs.
|
||||
|
||||
Running phpcs
|
||||
-------------
|
||||
|
||||
Now you can run phpcs:
|
||||
|
||||
$ cd modules/examples
|
||||
$ ../../vendor/bin/phpcs -ps
|
||||
// phpcs uses Examples' phpcs.xml.dist to verify coding standards.
|
||||
// -p shows you progress dots.
|
||||
// -s shows you sniff errors in the report.
|
||||
|
||||
If there are errors, they can sometimes be fixed with `phpcbf`, which is
|
||||
part of the `phpcs` package.
|
||||
|
||||
$ ../../vendor/bin/phpcbf
|
||||
// phpcbf now performs automated fixes.
|
||||
|
||||
Always look at the changes to see what `phpcbf` did.
|
||||
|
||||
And always re-run `phpcs` in order to discover whether `phpcbf` handled all the
|
||||
errors.
|
||||
|
||||
Installing eslint
|
||||
-----------------
|
||||
|
||||
`eslint` is a node.js tool. You can and probably should install it globally,
|
||||
since installing it locally would add files to the examples project.
|
||||
Instructions available here: https://www.npmjs.com/package/eslint
|
||||
|
||||
Examples has an `.eslintrc` file which defines the JavaScript coding standard.
|
||||
This file should be identical to the current Drupal core standard.
|
||||
|
||||
Running eslint
|
||||
--------------
|
||||
|
||||
You can run eslint this way:
|
||||
|
||||
$ cd /path/to/examples
|
||||
$ eslint .
|
||||
@@ -1,114 +0,0 @@
|
||||
Testing Drupal Examples for Developers
|
||||
======================================
|
||||
|
||||
The Drupal Examples for Developers project uses DrupalCI testing on drupal.org.
|
||||
|
||||
That means: It runs the testbot on every patch that is marked as 'Needs Review.'
|
||||
|
||||
Your patch might not get reviewed, and certainly won't get committed unless it
|
||||
passes the testbot.
|
||||
|
||||
The testbot runs a script that's in your Drupal installation called
|
||||
`core/scripts/run-tests.sh`. You can run `run-tests.sh` manually and approximate
|
||||
the testbot's behavior.
|
||||
|
||||
You can find information on how to run `run-tests.sh` locally here:
|
||||
https://www.drupal.org/node/645286
|
||||
|
||||
Examples is always targeted to the dev branch of Drupal core for the latest
|
||||
release. As of this writing, the latest release of Drupal core is 8.2.5, which
|
||||
means development for Examples should be against the Drupal 8.2.x development
|
||||
branch. When Drupal 8.3.0 is released, we'll start targeting Examples to 8.3.x,
|
||||
and so on.
|
||||
|
||||
You should at least run `run-tests.sh` locally against all the changes in your
|
||||
patch before uploading it.
|
||||
|
||||
Keep in mind that unless you know you're changing behavior that is being tested
|
||||
for, the tests are not at fault. :-)
|
||||
|
||||
Note also that, currently, using the `phpunit` tool under Drupal 8 will not find
|
||||
PHPUnit-based tests in submodules, such as phpunit_example. There is no
|
||||
suggested workaround for this, since there is no best practice to demonstrate as
|
||||
an example. There is, however, this issue in core:
|
||||
https://www.drupal.org/node/2499239
|
||||
|
||||
How To Run The Tests In The Drupal UI
|
||||
-------------------------------------
|
||||
|
||||
Generally, you should run tests from the command line. This is generally easier
|
||||
than using Drupal's testing UI. However, here's how you can do it that way:
|
||||
|
||||
Enable the Testing module.
|
||||
|
||||
Visit the test list page at `admin/config/development/testing`.
|
||||
|
||||
Since the tests are organized by module, you can search for a module name and
|
||||
get all the tests for that module. For instance, type in 'node_type_example' for
|
||||
all the tests related to that module.
|
||||
|
||||
Click the check boxes next to the tests you want to run. If you find this
|
||||
tedious, it's time to learn to use the command line. :-)
|
||||
|
||||
Click 'Run Tests.' You're now running the tests.
|
||||
|
||||
Step-by-step: How To Run The Tests.
|
||||
-----------------------------------
|
||||
|
||||
Begin with an installed Drupal codebase. Make a codebase, set up the database,
|
||||
etc. Note that you can use an existing Drupal instance but the best practice is
|
||||
to start fresh. Something not working right? Try a new installation.
|
||||
|
||||
Use the dev branch of core for the latest release of Drupal. As of this writing,
|
||||
it's 8.2.x. When Drupal 8.3.0 is released, we'll target 8.3.x.
|
||||
|
||||
Open the terminal window and move to the root directory of the Drupal
|
||||
installation:
|
||||
|
||||
$ cd path/to/drupal
|
||||
|
||||
Put Examples into the `modules/` folder of the Drupal installation. If you are
|
||||
doing development on Examples, you should have already checked out the git
|
||||
repository into `modules/`, like this:
|
||||
|
||||
$ git clone --branch 8.x-1.x https://git.drupal.org/project/examples.git modules/examples
|
||||
|
||||
Now you can run `run-tests.sh`, which, despite having a `.sh` suffix is not a
|
||||
shell script. It's a PHP script.
|
||||
|
||||
You'll use the `--directory` option to have the test runner scan the Examples
|
||||
module directory for tests.
|
||||
|
||||
Also, importantly, if your test site has its own URL, you'll need to supply that
|
||||
with the `--url` option. For instance, under MAMP, you must specify
|
||||
`--url http://localhost:8888/`.
|
||||
|
||||
You can also use `--concurrency` to speed up the test run, and `--browser` to
|
||||
see detailed test results in a web browser instead of just text output in the
|
||||
terminal.
|
||||
|
||||
$ php ./core/scripts/run-tests.sh --browser --concurrency 10 --url http://localhost:8888/ --directory modules/examples
|
||||
|
||||
This should run all the tests present in Examples. If you add a test and it
|
||||
doesn't appear in the list of tests to run, then you'll need to double-check
|
||||
that it's in the proper test namespace and that the class name (and thus the
|
||||
file name) ends in Test.
|
||||
|
||||
What Tests Should An Example Module Have?
|
||||
------------------------------------------
|
||||
|
||||
Examples has a checklist for each module:
|
||||
https://www.drupal.org/node/2209627
|
||||
|
||||
The reason we care about these tests is that we want the documentation
|
||||
of these APIs to be correct. If Core changes APIs, we want our tests to
|
||||
fail so that we know our documentation is incorrect.
|
||||
|
||||
Our list of required tests includes:
|
||||
* Functional tests which verifies a 200 result for each route/path defined by
|
||||
the module.
|
||||
* Functional tests of permission-based restrictions.
|
||||
* Functional tests which submit forms and verify that they behave as
|
||||
expected.
|
||||
* Unit tests of unit-testable code.
|
||||
* Other. More. Better.
|
||||
@@ -1,14 +0,0 @@
|
||||
name: 'AJAX Example'
|
||||
type: module
|
||||
description: 'An example module showing how to use Drupal AJAX forms.'
|
||||
package: 'Example modules'
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- drupal:node
|
||||
- examples:examples
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.x-dev'
|
||||
core: '8.x'
|
||||
project: 'examples'
|
||||
datestamp: 1513537386
|
||||
@@ -1,7 +0,0 @@
|
||||
ajax_example.library:
|
||||
version: 1.x
|
||||
css:
|
||||
base:
|
||||
css/ajax-example-base.css: {}
|
||||
js:
|
||||
js/ajax-example.js: {}
|
||||
@@ -1,72 +0,0 @@
|
||||
ajax_example.description:
|
||||
title: 'AJAX Example'
|
||||
route_name: 'ajax_example.description'
|
||||
expanded: TRUE
|
||||
|
||||
ajax_example.simplest:
|
||||
title: 'Simplest AJAX example'
|
||||
route_name: 'ajax_example.simplest'
|
||||
parent: ajax_example.description
|
||||
weight: 0
|
||||
|
||||
ajax_example.submit-driven:
|
||||
title: 'Submit-driven AJAX'
|
||||
route_name: 'ajax_example.submit_driven_ajax'
|
||||
parent: ajax_example.description
|
||||
weight: 1
|
||||
|
||||
ajax_example.render-link:
|
||||
title: 'AJAX link in a render array'
|
||||
route_name: 'ajax_example.ajax_link_render'
|
||||
parent: ajax_example.description
|
||||
weight: 2
|
||||
|
||||
ajax_example.wizard-example:
|
||||
title: 'Wizard example'
|
||||
route_name: 'ajax_example.wizard'
|
||||
parent: ajax_example.description
|
||||
weight: 2
|
||||
|
||||
ajax_example.wizard-examplenojs:
|
||||
title: 'Wizard example w/JS turned off'
|
||||
route_name: 'ajax_example.wizardnojs'
|
||||
parent: ajax_example.description
|
||||
weight: 3
|
||||
|
||||
ajax_example.autocomplete-user:
|
||||
title: 'Autocomplete user with entity_autocomplete'
|
||||
route_name: 'ajax_example.autocomplete_user'
|
||||
parent: ajax_example.description
|
||||
weight: 4
|
||||
|
||||
ajax_example.autotextfields:
|
||||
title: 'Generate textfields'
|
||||
route_name: 'ajax_example.autotextfields'
|
||||
parent: ajax_example.description
|
||||
weight: 5
|
||||
|
||||
ajax_example.dependent-dropdown:
|
||||
title: 'Dependent dropdown'
|
||||
route_name: 'ajax_example.dependent_dropdown'
|
||||
parent: ajax_example.description
|
||||
weight: 6
|
||||
ajax_example.dependent-dropdown-nojs:
|
||||
title: 'Dependent dropdown w/ no JS'
|
||||
route_name: 'ajax_example.dependent_dropdown'
|
||||
route_parameters:
|
||||
nojs: nojs
|
||||
parent: ajax_example.description
|
||||
weight: 6
|
||||
|
||||
ajax_example.dynamic-form-sections:
|
||||
title: 'Dynamic form sections'
|
||||
route_name: 'ajax_example.dynamic_form_sections'
|
||||
parent: ajax_example.description
|
||||
weight: 10
|
||||
ajax_example.dynamic-form-sections-nojs:
|
||||
title: 'Dynamic form sections w/ no JS'
|
||||
route_name: 'ajax_example.dynamic_form_sections'
|
||||
route_parameters:
|
||||
nojs: nojs
|
||||
parent: ajax_example.description
|
||||
weight: 10
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* AJAX Examples module file with basic examples.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup ajax_example Example: AJAX
|
||||
* @ingroup examples
|
||||
* @{
|
||||
* These examples show basic AJAX concepts.
|
||||
*
|
||||
* General documentation is available at
|
||||
* @link ajax AJAX Framework documentation @endlink and at the
|
||||
* @link http://drupal.org/node/752056 AJAX Forms handbook page @endlink.
|
||||
*
|
||||
* The several examples here demonstrate basic AJAX usage.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @} End of "defgroup ajax_example".
|
||||
*/
|
||||
@@ -1,96 +0,0 @@
|
||||
ajax_example.description:
|
||||
path: 'examples/ajax-example'
|
||||
defaults:
|
||||
_controller: '\Drupal\ajax_example\Controller\AjaxExampleController::description'
|
||||
_title: 'AJAX Example'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
ajax_example.simplest:
|
||||
path: 'examples/ajax-example/simplest'
|
||||
defaults:
|
||||
_form: '\Drupal\ajax_example\Form\Simplest'
|
||||
_title: 'Simplest AJAX example'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
ajax_example.autotextfields:
|
||||
path: 'examples/ajax-example/autotextfields'
|
||||
defaults:
|
||||
_form: '\Drupal\ajax_example\Form\Autotextfields'
|
||||
_title: 'Generate textfields'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
ajax_example.submit_driven_ajax:
|
||||
path: 'examples/ajax-example/submit-driven-ajax'
|
||||
defaults:
|
||||
_form: '\Drupal\ajax_example\Form\SubmitDriven'
|
||||
_title: 'Submit-driven AJAX'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
ajax_example.dependent_dropdown:
|
||||
path: 'examples/ajax-example/dependent-dropdown/{nojs}'
|
||||
defaults:
|
||||
_form: '\Drupal\ajax_example\Form\DependentDropdown'
|
||||
_title: 'Dependent dropdown'
|
||||
nojs: ajax
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
ajax_example.dynamic_form_sections:
|
||||
path: 'examples/ajax-example/dynamic-form-sections/{nojs}'
|
||||
defaults:
|
||||
_form: '\Drupal\ajax_example\Form\DynamicFormSections'
|
||||
_title: 'Dynamic form sections'
|
||||
nojs: 'ajax'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
ajax_example.wizard:
|
||||
path: 'examples/ajax-example/wizard'
|
||||
defaults:
|
||||
_form: '\Drupal\ajax_example\Form\Wizard'
|
||||
_title: 'Wizard with graceful degradation'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
ajax_example.wizardnojs:
|
||||
path: 'examples/ajax-example/wizard-nojs/{no_js_use}'
|
||||
defaults:
|
||||
_form: '\Drupal\ajax_example\Form\Wizard'
|
||||
_title: 'Wizard with graceful degradation, w/JS turned off'
|
||||
no_js_use: TRUE
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
ajax_example.ajax_link_render:
|
||||
path: 'examples/ajax-example/ajax-link-renderable'
|
||||
defaults:
|
||||
_controller: '\Drupal\ajax_example\Controller\AjaxExampleController::renderLinkRenderableArray'
|
||||
_title: 'AJAX link from a render array'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
# This route is for an AJAX callback. It is used by the AJAX system on
|
||||
# ajax_example.ajax_link_render. It has a {nojs} parameter, which gives us
|
||||
# a way to know whether the request is an AJAX request or is from some other
|
||||
# source.
|
||||
ajax_example.ajax_link_callback:
|
||||
path: 'examples/ajax-example/ajax-link-callback/{nojs}'
|
||||
defaults:
|
||||
_controller: '\Drupal\ajax_example\Controller\AjaxExampleController::ajaxLinkCallback'
|
||||
# We provide a default value for {nojs}, so that it can be an optional
|
||||
# parameter.
|
||||
nojs: 'nojs'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
|
||||
ajax_example.autocomplete_user:
|
||||
path: 'examples/ajax_example/user_autocomplete'
|
||||
defaults:
|
||||
_form: '\Drupal\ajax_example\Form\EntityAutocomplete'
|
||||
_title: 'Autocomplete users with entity_autocomplete.'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* @file
|
||||
* CSS for ajax_example.
|
||||
*/
|
||||
|
||||
/* Put some buttons inline, for nicer formatting. */
|
||||
.ajax-example-inline {
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* @file
|
||||
* JavaScript for ajax_example.
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
// Re-enable form elements that are disabled for non-ajax situations.
|
||||
Drupal.behaviors.enableFormItemsForAjaxForms = {
|
||||
attach: function () {
|
||||
// If ajax is enabled, we want to hide items that are marked as hidden in
|
||||
// our example.
|
||||
if (Drupal.ajax) {
|
||||
$('.ajax-example-hide').hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ajax_example\Controller;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\AppendCommand;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\examples\Utility\DescriptionTemplateTrait;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Controller routines for AJAX example routes.
|
||||
*/
|
||||
class AjaxExampleController extends ControllerBase {
|
||||
|
||||
use DescriptionTemplateTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getModuleName() {
|
||||
return 'ajax_example';
|
||||
}
|
||||
|
||||
/**
|
||||
* Demonstrates a clickable AJAX-enabled link using the 'use-ajax' class.
|
||||
*
|
||||
* Because of the 'use-ajax' class applied here, the link submission is done
|
||||
* without a page refresh.
|
||||
*
|
||||
* When using the AJAX framework outside the context of a form or a renderable
|
||||
* array of type 'link', you have to include ajax.js explicitly.
|
||||
*
|
||||
* @return array
|
||||
* Form API array.
|
||||
*
|
||||
* @ingroup ajax_example
|
||||
*/
|
||||
public function renderLinkRenderableArray() {
|
||||
$build['my_div'] = [
|
||||
'#markup' => $this->t('
|
||||
The link below has been rendered as an element with the #ajax property, so if
|
||||
javascript is enabled, ajax.js will try to submit it via an AJAX call instead
|
||||
of a normal page load. The URL also contains the "/nojs/" magic string, which
|
||||
is stripped if javascript is enabled, allowing the server code to tell by the
|
||||
URL whether JS was enabled or not, letting it do different things based on that.'),
|
||||
];
|
||||
// We'll add a nice border element for our demo.
|
||||
$build['ajax_link'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('This is the AJAX link'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
// We build the AJAX link.
|
||||
$build['ajax_link']['link'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => $this->t('Click me'),
|
||||
// We have to ensure that Drupal's Ajax system is loaded.
|
||||
'#attached' => ['library' => ['core/drupal.ajax']],
|
||||
// We add the 'use-ajax' class so that Drupal's AJAX system can spring
|
||||
// into action.
|
||||
'#attributes' => ['class' => ['use-ajax']],
|
||||
// The URL for this link element is the callback. In our case, it's route
|
||||
// ajax_example.ajax_link_callback, which maps to ajaxLinkCallback()
|
||||
// below. The route has a /{nojs} section, which is how the callback can
|
||||
// know whether the request was made by AJAX or some other means where
|
||||
// JavaScript won't be able to handle the result. If the {nojs} part of
|
||||
// the path is replaced with 'ajax', then the request was made by AJAX.
|
||||
'#url' => Url::fromRoute('ajax_example.ajax_link_callback', ['nojs' => 'ajax']),
|
||||
];
|
||||
// We provide a DIV that AJAX can append some text into.
|
||||
$build['ajax_link']['destination'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => ['id' => ['ajax-example-destination-div']],
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for link example.
|
||||
*
|
||||
* Takes different logic paths based on whether Javascript was enabled.
|
||||
* If $type == 'ajax', it tells this function that ajax.js has rewritten
|
||||
* the URL and thus we are doing an AJAX and can return an array of commands.
|
||||
*
|
||||
* @param string $nojs
|
||||
* Either 'ajax' or 'nojs. Type is simply the normal URL argument to this
|
||||
* URL.
|
||||
*
|
||||
* @return string|array
|
||||
* If $type == 'ajax', returns an array of AJAX Commands.
|
||||
* Otherwise, just returns the content, which will end up being a page.
|
||||
*/
|
||||
public function ajaxLinkCallback($nojs = 'ajax') {
|
||||
// Determine whether the request is coming from AJAX or not.
|
||||
if ($nojs == 'ajax') {
|
||||
$output = $this->t("This is some content delivered via AJAX");
|
||||
$response = new AjaxResponse();
|
||||
$response->addCommand(new AppendCommand('#ajax-example-destination-div', $output));
|
||||
|
||||
// See ajax_example_advanced.inc for more details on the available
|
||||
// commands and how to use them.
|
||||
// $page = array('#type' => 'ajax', '#commands' => $commands);
|
||||
// ajax_deliver($response);
|
||||
return $response;
|
||||
}
|
||||
$response = new Response($this->t("This is some content delivered via a page load."));
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ajax_example\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Show textfields based on AJAX-enabled checkbox clicks.
|
||||
*
|
||||
* @ingroup ajax_example
|
||||
*/
|
||||
class Autotextfields extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'ajax_example_autotextfields';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* This form has two checkboxes which the user can check in order to then
|
||||
* reveal the first and/or last name text fields.
|
||||
*
|
||||
* We could perform this behavior with #states. We might not want to if, for
|
||||
* instance, we wanted to require a name, but let the user choose whether
|
||||
* to enter first or last or both.
|
||||
*
|
||||
* For all the requests this class gets, the buildForm() method will always be
|
||||
* called. If an AJAX request comes in, the form state will be set to the
|
||||
* state the user changed that caused the AJAX request. So if the user enabled
|
||||
* one of our checkboxes, it will be checked in $form_state.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['description'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => $this->t('This form demonstrates changing the status of form elements through AJAX requests.'),
|
||||
];
|
||||
$form['ask_first_name'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Ask me my first name'),
|
||||
'#ajax' => [
|
||||
'callback' => '::textfieldsCallback',
|
||||
'wrapper' => 'textfields-container',
|
||||
'effect' => 'fade',
|
||||
],
|
||||
];
|
||||
$form['ask_last_name'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Ask me my last name'),
|
||||
'#ajax' => [
|
||||
'callback' => '::textfieldsCallback',
|
||||
'wrapper' => 'textfields-container',
|
||||
'effect' => 'fade',
|
||||
],
|
||||
];
|
||||
|
||||
// Wrap textfields in a container. This container will be replaced through
|
||||
// AJAX.
|
||||
$form['textfields_container'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => ['id' => 'textfields-container'],
|
||||
];
|
||||
$form['textfields_container']['textfields'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t("Generated text fields for first and last name"),
|
||||
'#description' => t('This is where we put automatically generated textfields'),
|
||||
];
|
||||
|
||||
// This form is rebuilt on all requests, so whether or not the request comes
|
||||
// from AJAX, we should rebuild everything based on the form state.
|
||||
// Checkbox values are expressed as 1 or 0, so we have to be sure to compare
|
||||
// type as well as value.
|
||||
if ($form_state->getValue('ask_first_name', NULL) === 1) {
|
||||
$form['textfields_container']['textfields']['first_name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('First Name'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
}
|
||||
if ($form_state->getValue('ask_last_name', NULL) === 1) {
|
||||
$form['textfields_container']['textfields']['last_name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Last Name'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
}
|
||||
|
||||
$form['textfields_container']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Click Me'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
drupal_set_message(
|
||||
$this->t('Submit handler: First name: @first_name Last name: @last_name',
|
||||
[
|
||||
'@first_name' => $form_state->getValue('first_name', 'n/a'),
|
||||
'@last_name' => $form_state->getValue('last_name', 'n/a'),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for ajax_example_autotextfields.
|
||||
*
|
||||
* Selects the piece of the form we want to use as replacement markup and
|
||||
* returns it as a form (renderable array).
|
||||
*/
|
||||
public function textfieldsCallback($form, FormStateInterface $form_state) {
|
||||
return $form['textfields_container'];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ajax_example\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Link;
|
||||
|
||||
/**
|
||||
* Re-populate a dropdown based on form state.
|
||||
*/
|
||||
class DependentDropdown extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'ajax_example_dependentdropdown';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* The $nojs parameter is specified as a path parameter on the route.
|
||||
*
|
||||
* @see ajax_example.routing.yml
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $nojs = NULL) {
|
||||
// Add our CSS and tiny JS to hide things when they should be hidden.
|
||||
$form['#attached']['library'][] = 'ajax_example/ajax_example.library';
|
||||
|
||||
// Explanatory text with helpful links.
|
||||
$form['info'] = [
|
||||
'#markup' =>
|
||||
$this->t('<p>Like other examples in this module, this form has a path that
|
||||
can be modified with /nojs to simulate its behavior without JavaScript.
|
||||
</p><ul>
|
||||
<li>@try_it_without_ajax</li>
|
||||
<li>@try_it_with_ajax</li>
|
||||
</ul>',
|
||||
[
|
||||
'@try_it_without_ajax' => Link::createFromRoute(
|
||||
$this->t('Try it without AJAX'),
|
||||
'ajax_example.dependent_dropdown', ['nojs' => 'nojs'])
|
||||
->toString(),
|
||||
'@try_it_with_ajax' => Link::createFromRoute(
|
||||
$this->t('Try it with AJAX'),
|
||||
'ajax_example.dependent_dropdown')
|
||||
->toString(),
|
||||
]
|
||||
),
|
||||
];
|
||||
|
||||
// Our first dropdown lets us select a family of instruments: String,
|
||||
// Woodwind, Brass, or Percussion.
|
||||
$instrument_family_options = static::getFirstDropdownOptions();
|
||||
// When the AJAX request occurs, this form will be build in order to process
|
||||
// form state before the AJAX callback is called. We can use this
|
||||
// opportunity to populate the form as we wish based on the changes to the
|
||||
// form that caused the AJAX request. If the user caused the AJAX request,
|
||||
// then it would have been setting a value for instrument_family_options.
|
||||
// So if there's a value in that dropdown before we build it here, we grab
|
||||
// it's value to help us build the specific instrument dropdown. Otherwise
|
||||
// we can just use the value of the first item as the default value.
|
||||
if (empty($form_state->getValue('instrument_family_dropdown'))) {
|
||||
// Use a default value.
|
||||
$selected_family = key($instrument_family_options);
|
||||
}
|
||||
else {
|
||||
// Get the value if it already exists.
|
||||
$selected_family = $form_state->getValue('instrument_family_dropdown');
|
||||
}
|
||||
|
||||
$form['instrument_family_fieldset'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Choose an instrument family'),
|
||||
];
|
||||
$form['instrument_family_fieldset']['instrument_family_dropdown'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Instrument Type'),
|
||||
'#options' => $instrument_family_options,
|
||||
'#default_value' => $selected_family,
|
||||
// Bind an ajax callback to the change event (which is the default for the
|
||||
// select form type) of the first dropdown. It will replace the second
|
||||
// dropdown when rebuilt.
|
||||
'#ajax' => [
|
||||
// When 'event' occurs, Drupal will perform an ajax request in the
|
||||
// background. Usually the default value is sufficient (eg. change for
|
||||
// select elements), but valid values include any jQuery event,
|
||||
// most notably 'mousedown', 'blur', and 'submit'.
|
||||
'callback' => '::instrumentDropdownCallback',
|
||||
'wrapper' => 'instrument-fieldset-container',
|
||||
],
|
||||
];
|
||||
// Since we don't know if the user has js or not, we always need to output
|
||||
// this element, then hide it with with css if javascript is enabled.
|
||||
$form['instrument_family_fieldset']['choose_family'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Choose'),
|
||||
'#attributes' => ['class' => ['ajax-example-hide', 'ajax-example-inline']],
|
||||
];
|
||||
// We are using the path parameter $nojs to signal when to simulate the
|
||||
// the user turning off JavaScript. We'll remove all the AJAX elements. This
|
||||
// is not required, and is here so that we can demonstrate a graceful
|
||||
// fallback without having to turn off JavaScript.
|
||||
if ($nojs == 'nojs') {
|
||||
// Removing the #ajax element tells the system not to use AJAX.
|
||||
unset($form['instrument_family_fieldset']['instrument_family_dropdown']['#ajax']);
|
||||
// Removing the ajax-example-hide class from the Choose button ensures
|
||||
// that our JavaScript won't hide it.
|
||||
unset($form['instrument_family_fieldset']['choose_family']['#attributes']);
|
||||
}
|
||||
|
||||
// Since we're managing state for this whole fieldset (both the dropdown
|
||||
// and enabling the Submit button), we want to replace the whole thing
|
||||
// on AJAX requests. That's why we put it in this container.
|
||||
$form['instrument_fieldset_container'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => ['id' => 'instrument-fieldset-container'],
|
||||
];
|
||||
// Build the instrument field set.
|
||||
$form['instrument_fieldset_container']['instrument_fieldset'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Choose an instrument'),
|
||||
];
|
||||
$form['instrument_fieldset_container']['instrument_fieldset']['instrument_dropdown'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $instrument_family_options[$selected_family] . ' ' . $this->t('Instruments'),
|
||||
// When the form is rebuilt during ajax processing, the $selected_family
|
||||
// variable will now have the new value and so the options will change.
|
||||
'#options' => static::getSecondDropdownOptions($selected_family),
|
||||
'#default_value' => !empty($form_state->getValue('instrument_dropdown')) ? $form_state->getValue('instrument_dropdown') : '',
|
||||
];
|
||||
$form['instrument_fieldset_container']['instrument_fieldset']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Submit'),
|
||||
];
|
||||
// We might normally use #state to disable the instrument fields based on
|
||||
// the instrument family fields. But since the premise is that we don't have
|
||||
// JavaScript running, #state won't work either. We have to set up the state
|
||||
// of the instrument fieldset here, based on the selected instrument family.
|
||||
if ($selected_family == 'none') {
|
||||
$form['instrument_fieldset_container']['instrument_fieldset']['instrument_dropdown']['#title'] =
|
||||
$this->t('You must choose an instrument family.');
|
||||
$form['instrument_fieldset_container']['instrument_fieldset']['instrument_dropdown']['#disabled'] = TRUE;
|
||||
$form['instrument_fieldset_container']['instrument_fieldset']['submit']['#disabled'] = TRUE;
|
||||
}
|
||||
else {
|
||||
$form['instrument_fieldset_container']['instrument_fieldset']['instrument_dropdown']['#disabled'] = FALSE;
|
||||
$form['instrument_fieldset_container']['instrument_fieldset']['submit']['#disabled'] = FALSE;
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$trigger = (string) $form_state->getTriggeringElement()['#value'];
|
||||
switch ($trigger) {
|
||||
case 'Submit':
|
||||
// Submit: We're done.
|
||||
drupal_set_message($this->t('Your values have been submitted. Instrument family: @family, Instrument: @instrument', [
|
||||
'@family' => $form_state->getValue('instrument_family_dropdown'),
|
||||
'@instrument' => $form_state->getValue('instrument_dropdown'),
|
||||
]));
|
||||
return;
|
||||
}
|
||||
// 'Choose' or anything else will cause rebuild of the form and present
|
||||
// it again.
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a new dropdown based on the AJAX call.
|
||||
*
|
||||
* This callback will occur *after* the form has been rebuilt by buildForm().
|
||||
* Since that's the case, the form should contain the right values for
|
||||
* instrument_dropdown.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* The portion of the render structure that will replace the
|
||||
* instrument-dropdown-replace form element.
|
||||
*/
|
||||
public function instrumentDropdownCallback(array $form, FormStateInterface $form_state) {
|
||||
return $form['instrument_fieldset_container'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to populate the first dropdown.
|
||||
*
|
||||
* This would normally be pulling data from the database.
|
||||
*
|
||||
* @return array
|
||||
* Dropdown options.
|
||||
*/
|
||||
public static function getFirstDropdownOptions() {
|
||||
return [
|
||||
'none' => 'none',
|
||||
'String' => 'String',
|
||||
'Woodwind' => 'Woodwind',
|
||||
'Brass' => 'Brass',
|
||||
'Percussion' => 'Percussion',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to populate the second dropdown.
|
||||
*
|
||||
* This would normally be pulling data from the database.
|
||||
*
|
||||
* @param string $key
|
||||
* This will determine which set of options is returned.
|
||||
*
|
||||
* @return array
|
||||
* Dropdown options
|
||||
*/
|
||||
public static function getSecondDropdownOptions($key = '') {
|
||||
switch ($key) {
|
||||
case 'String':
|
||||
$options = [
|
||||
'Violin' => 'Violin',
|
||||
'Viola' => 'Viola',
|
||||
'Cello' => 'Cello',
|
||||
'Double Bass' => 'Double Bass',
|
||||
];
|
||||
break;
|
||||
|
||||
case 'Woodwind':
|
||||
$options = [
|
||||
'Flute' => 'Flute',
|
||||
'Clarinet' => 'Clarinet',
|
||||
'Oboe' => 'Oboe',
|
||||
'Bassoon' => 'Bassoon',
|
||||
];
|
||||
break;
|
||||
|
||||
case 'Brass':
|
||||
$options = [
|
||||
'Trumpet' => 'Trumpet',
|
||||
'Trombone' => 'Trombone',
|
||||
'French Horn' => 'French Horn',
|
||||
'Euphonium' => 'Euphonium',
|
||||
];
|
||||
break;
|
||||
|
||||
case 'Percussion':
|
||||
$options = [
|
||||
'Bass Drum' => 'Bass Drum',
|
||||
'Timpani' => 'Timpani',
|
||||
'Snare Drum' => 'Snare Drum',
|
||||
'Tambourine' => 'Tambourine',
|
||||
];
|
||||
break;
|
||||
|
||||
default:
|
||||
$options = ['none' => 'none'];
|
||||
break;
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
}
|
||||
-222
@@ -1,222 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ajax_example\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Link;
|
||||
|
||||
/**
|
||||
* Dynamically-enabled form with graceful no-JS degradation.
|
||||
*
|
||||
* Example of a form with portions dynamically enabled or disabled, but with
|
||||
* graceful degradation in the case of no JavaScript.
|
||||
*
|
||||
* The idea here is that certain parts of the form don't need to be displayed
|
||||
* unless a given option is selected, but then they should be displayed and
|
||||
* configured.
|
||||
*
|
||||
* The third $no_js_use argument is strictly for demonstrating operation
|
||||
* without JavaScript, without making the user/developer turn off JavaScript.
|
||||
*/
|
||||
class DynamicFormSections extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'ajax_example_dynamicsectiondegrades';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $nojs = NULL) {
|
||||
// Add our CSS and tiny JS to hide things when they should be hidden.
|
||||
$form['#attached']['library'][] = 'ajax_example/ajax_example.library';
|
||||
|
||||
// Explanatory text with helpful links.
|
||||
$form['info'] = [
|
||||
'#markup' =>
|
||||
$this->t('<p>Like other examples in this module, this form has a path that
|
||||
can be modified with /nojs to simulate its behavior without JavaScript.
|
||||
</p><ul>
|
||||
<li>@try_it_without_ajax</li>
|
||||
<li>@try_it_with_ajax</li>
|
||||
</ul>',
|
||||
[
|
||||
'@try_it_without_ajax' => Link::createFromRoute(
|
||||
$this->t('Try it without AJAX'),
|
||||
'ajax_example.dynamic_form_sections', ['nojs' => 'nojs'])
|
||||
->toString(),
|
||||
'@try_it_with_ajax' => Link::createFromRoute(
|
||||
$this->t('Try it with AJAX'),
|
||||
'ajax_example.dynamic_form_sections')
|
||||
->toString(),
|
||||
]
|
||||
),
|
||||
];
|
||||
|
||||
$form['question_type_select'] = [
|
||||
// This is our select dropdown.
|
||||
'#type' => 'select',
|
||||
'#title' => t('Question style'),
|
||||
// We have a variety of form items you can use to get input from the user.
|
||||
'#options' => [
|
||||
'Choose question style' => 'Choose question style',
|
||||
'Multiple Choice' => 'Multiple Choice',
|
||||
'True/False' => 'True/False',
|
||||
'Fill-in-the-blanks' => 'Fill-in-the-blanks',
|
||||
],
|
||||
// The #ajax section tells the AJAX system that whenever this dropdown
|
||||
// emits an event, it should call the callback and put the resulting
|
||||
// content into the wrapper we specify. The questions-fieldset-wrapper is
|
||||
// defined below.
|
||||
'#ajax' => [
|
||||
'wrapper' => 'questions-fieldset-wrapper',
|
||||
'callback' => '::promptCallback',
|
||||
],
|
||||
];
|
||||
// The CSS for this module hides this next button if JS is enabled.
|
||||
$form['question_type_submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Choose'),
|
||||
'#attributes' => ['class' => ['ajax-example-inline']],
|
||||
// No need to validate when submitting this.
|
||||
'#limit_validation_errors' => [],
|
||||
'#validate' => [],
|
||||
];
|
||||
|
||||
// This section allows us to demonstrate no-AJAX use without turning off
|
||||
// javascript in the browser.
|
||||
if ($nojs != 'nojs') {
|
||||
// Allow JavaScript to hide the choose button if we're using AJAX.
|
||||
$form['question_type_submit']['#attributes']['class'][] = 'ajax-example-hide';
|
||||
}
|
||||
else {
|
||||
// Remove #ajax from the above, so it won't perform AJAX behaviors.
|
||||
unset($form['question_type_select']['#ajax']);
|
||||
}
|
||||
|
||||
// This fieldset just serves as a container for the part of the form
|
||||
// that gets rebuilt. It has a nice line around it so you can see it.
|
||||
$form['questions_fieldset'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Stuff will appear here'),
|
||||
'#open' => TRUE,
|
||||
// We set the ID of this fieldset to questions-fieldset-wrapper so the
|
||||
// AJAX command can replace it.
|
||||
'#attributes' => ['id' => 'questions-fieldset-wrapper'],
|
||||
];
|
||||
|
||||
// When the AJAX request comes in, or when the user hit 'Submit' if there is
|
||||
// no JavaScript, the form state will tell us what the user has selected
|
||||
// from the dropdown. We can look at the value of the dropdown to determine
|
||||
// which secondary form to display.
|
||||
$question_type = $form_state->getValue('question_type_select');
|
||||
if (!empty($question_type) && $question_type !== 'Choose question style') {
|
||||
|
||||
$form['questions_fieldset']['question'] = [
|
||||
'#markup' => t('Who was the first president of the U.S.?'),
|
||||
];
|
||||
|
||||
// Build up a secondary form, based on the type of question the user
|
||||
// chose.
|
||||
switch ($question_type) {
|
||||
case 'Multiple Choice':
|
||||
$form['questions_fieldset']['question'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Who was the first president of the United States'),
|
||||
'#options' => [
|
||||
'George Bush' => 'George Bush',
|
||||
'Adam McGuire' => 'Adam McGuire',
|
||||
'Abraham Lincoln' => 'Abraham Lincoln',
|
||||
'George Washington' => 'George Washington',
|
||||
],
|
||||
|
||||
];
|
||||
break;
|
||||
|
||||
case 'True/False':
|
||||
$form['questions_fieldset']['question'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Was George Washington the first president of the United States?'),
|
||||
'#options' => [
|
||||
'George Washington' => 'True',
|
||||
0 => 'False',
|
||||
],
|
||||
'#description' => $this->t('Click "True" if you think George Washington was the first president of the United States.'),
|
||||
];
|
||||
break;
|
||||
|
||||
case 'Fill-in-the-blanks':
|
||||
$form['questions_fieldset']['question'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Who was the first president of the United States'),
|
||||
'#description' => $this->t('Please type the correct answer to the question.'),
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
$form['questions_fieldset']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Submit your answer'),
|
||||
];
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final submit handler.
|
||||
*
|
||||
* Reports what values were finally set.
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// This is only executed when a button is pressed, not when the AJAXfield
|
||||
// select is changed.
|
||||
// Now handle the case of the next, previous, and submit buttons.
|
||||
// Only submit will result in actual submission, all others rebuild.
|
||||
if ($form_state->getValue('question_type_submit') == 'Choose') {
|
||||
$form_state->setValue('question_type_select', $form_state->getUserInput()['question_type_select']);
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
if ($form_state->getValue('submit') == 'Submit your answer') {
|
||||
$form_state->setRebuild(FALSE);
|
||||
$answer = $form_state->getValue('question');
|
||||
// Special handling for the checkbox.
|
||||
if ($answer == 1 && $form['questions_fieldset']['question']['#type'] == 'checkbox') {
|
||||
$answer = $form['questions_fieldset']['question']['#title'];
|
||||
}
|
||||
if ($answer == $this->t('George Washington')) {
|
||||
drupal_set_message($this->t('You got the right answer: @answer', ['@answer' => $answer]));
|
||||
}
|
||||
else {
|
||||
drupal_set_message($this->t('Sorry, your answer (@answer) is wrong', ['@answer' => $answer]));
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Sets the form to be rebuilt after processing.
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the select element.
|
||||
*
|
||||
* Since the questions_fieldset part of the form has already been built during
|
||||
* the AJAX request, we can return only that part of the form to the AJAX
|
||||
* request, and it will insert that part into questions-fieldset-wrapper.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* The form structure.
|
||||
*/
|
||||
public function promptCallback(array $form, FormStateInterface $form_state) {
|
||||
return $form['questions_fieldset'];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ajax_example\Form;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* A simple autocomplete form which looks up usernames.
|
||||
*
|
||||
* @ingroup ajax_example
|
||||
*/
|
||||
class EntityAutocomplete implements FormInterface, ContainerInjectionInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager service.
|
||||
*
|
||||
* We need this for the submit handler.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Container injection factory.
|
||||
*
|
||||
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
|
||||
* The service discovery container.
|
||||
*
|
||||
* @return self
|
||||
* The form object.
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
$form = new static(
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
$form->setStringTranslation($container->get('string_translation'));
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager service.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'ajax_example_autocomplete_user';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['info'] = [
|
||||
'#markup' => '<div>' . t("This example uses the entity_autocomplete form "
|
||||
. "element to select users. You'll need a few users on your system for "
|
||||
. "it to make sense.") . '</div>',
|
||||
];
|
||||
|
||||
// Here we use the delightful entity_autocomplete form element. It allows us
|
||||
// to consistently select entities. See https://www.drupal.org/node/2418529.
|
||||
$form['users'] = [
|
||||
// A type of entity_autocomplete lets Drupal know it should autocomplete
|
||||
// entities.
|
||||
'#type' => 'entity_autocomplete',
|
||||
// We can specify entity types to autocomplete.
|
||||
'#target_type' => 'user',
|
||||
// Specifying #tags as TRUE allows for multiple selections, separated by
|
||||
// commas.
|
||||
'#tags' => TRUE,
|
||||
'#title' => t('Choose a user. Separate with commas.'),
|
||||
];
|
||||
|
||||
$form['actions'] = [
|
||||
'#type' => 'actions',
|
||||
];
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Submit'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Here we validate and signal an error if there are no users selected.
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$state_users = $form_state->getValue('users');
|
||||
if (empty($state_users)) {
|
||||
$form_state->setErrorByName('users', 'There were no users selected.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* On submit, show the user the names of the users they selected.
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$state_users = $form_state->getValue('users');
|
||||
$users = [];
|
||||
foreach ($state_users as $state_user) {
|
||||
$uid = $state_user['target_id'];
|
||||
$users[] = $this->entityTypeManager->getStorage('user')->load($uid)->getUsername();
|
||||
}
|
||||
drupal_set_message('These are your users: ' . implode(' ', $users));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ajax_example\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* A relatively simple AJAX demonstration form.
|
||||
*/
|
||||
class Simplest extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'ajax_example_simplest';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['changethis'] = [
|
||||
'#title' => $this->t("Choose something and explain why"),
|
||||
'#type' => 'select',
|
||||
'#options' => [
|
||||
'one' => 'one',
|
||||
'two' => 'two',
|
||||
'three' => 'three',
|
||||
],
|
||||
'#ajax' => [
|
||||
// #ajax has two required keys: callback and wrapper.
|
||||
// 'callback' is a function that will be called when this element
|
||||
// changes.
|
||||
'callback' => '::promptCallback',
|
||||
// 'wrapper' is the HTML id of the page element that will be replaced.
|
||||
'wrapper' => 'replace-textfield-container',
|
||||
],
|
||||
];
|
||||
|
||||
// The 'replace-textfield-container' container will be replaced whenever
|
||||
// 'changethis' is updated.
|
||||
$form['replace_textfield_container'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => ['id' => 'replace-textfield-container'],
|
||||
];
|
||||
$form['replace_textfield_container']['replace_textfield'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t("Why"),
|
||||
];
|
||||
|
||||
// An AJAX request calls the form builder function for every change.
|
||||
// We can change how we build the form based on $form_state.
|
||||
$value = $form_state->getValue('changethis');
|
||||
// The getValue() method returns NULL by default if the form element does
|
||||
// not exist. It won't exist yet if we're building it for the first time.
|
||||
if ($value !== NULL) {
|
||||
$form['replace_textfield_container']['replace_textfield']['#description'] =
|
||||
$this->t("Say why you chose '@value'", ['@value' => $value]);
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// No-op. Our form doesn't need a submit handler, because the form is never
|
||||
// submitted. We add the method here so we fulfill FormInterface.
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles switching the available regions based on the selected theme.
|
||||
*/
|
||||
public function promptCallback($form, FormStateInterface $form_state) {
|
||||
return $form['replace_textfield_container'];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ajax_example\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Submit a form without a page reload.
|
||||
*/
|
||||
class SubmitDriven extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'ajax_example_autotextfields';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
// This container wil be replaced by AJAX.
|
||||
$form['container'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => ['id' => 'box-container'],
|
||||
];
|
||||
// The box contains some markup that we can change on a submit request.
|
||||
$form['container']['box'] = [
|
||||
'#type' => 'markup',
|
||||
'#markup' => '<h1>Initial markup for box</h1>',
|
||||
];
|
||||
|
||||
$form['submit'] = [
|
||||
'#type' => 'submit',
|
||||
// The AJAX handler will call our callback, and will replace whatever page
|
||||
// element has id box-container.
|
||||
'#ajax' => [
|
||||
'callback' => '::promptCallback',
|
||||
'wrapper' => 'box-container',
|
||||
],
|
||||
'#value' => $this->t('Submit'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for submit_driven example.
|
||||
*
|
||||
* Select the 'box' element, change the markup in it, and return it as a
|
||||
* renderable array.
|
||||
*
|
||||
* @return array
|
||||
* Renderable array (the box element)
|
||||
*/
|
||||
public function promptCallback(array &$form, FormStateInterface $form_state) {
|
||||
// In most cases, it is recommended that you put this logic in form
|
||||
// generation rather than the callback. Submit driven forms are an
|
||||
// exception, because you may not want to return the form at all.
|
||||
$element = $form['container'];
|
||||
$element['box']['#markup'] = "Clicked submit ({$form_state->getValue('op')}): " . date('c');
|
||||
return $element;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ajax_example\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\HtmlCommand;
|
||||
|
||||
/**
|
||||
* AJAX example wizard.
|
||||
*/
|
||||
class Wizard extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'ajax_example_wizard';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $no_js_use = FALSE) {
|
||||
$url = Url::fromUri('internal:/examples/ajax-example/wizard-nojs');
|
||||
$link = Link::fromTextAndUrl($this->t('examples/ajax-example/wizard-nojs'), $url)
|
||||
->toString();
|
||||
|
||||
// Prepare link for multiple arguments.
|
||||
$urltwo = Url::fromUri('internal:/examples/ajax-example/wizard');
|
||||
$linktwo = Link::fromTextAndUrl($this->t('examples/ajax-example/wizard'), $urltwo)
|
||||
->toString();
|
||||
|
||||
// We want to deal with hierarchical form values.
|
||||
$form['#tree'] = TRUE;
|
||||
$form['description'] = [
|
||||
'#markup' => t('This example is a step-by-step wizard. The @link does it without page reloads; the @link1 is the same code but simulates a non-javascript environment, showing it with page reloads.', [
|
||||
'@link' => $linktwo,
|
||||
'@link1' => $link,
|
||||
]),
|
||||
];
|
||||
|
||||
$form['step'] = [
|
||||
'#type' => 'hidden',
|
||||
'#value' => !empty($form_state->getValue('step')) ? $form_state->getValue('step') : 1,
|
||||
];
|
||||
print_r($form_state->getValue('step'));
|
||||
|
||||
if ($form['step']['#value'] == 1) {
|
||||
$form['step1'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Step 1: Personal details'),
|
||||
];
|
||||
$form['step1']['name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Your name'),
|
||||
'#default_value' => empty($form_state->getValue([
|
||||
'step1',
|
||||
'name',
|
||||
]) ? '' : $form_state->getValue(['step1', 'name'])),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['next'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Next step'),
|
||||
'#ajax' => [
|
||||
'wrapper' => 'ajax-example-wizard',
|
||||
'callback' => '::prompt',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// This simply allows us to demonstrate no-javascript use without
|
||||
// actually turning off javascript in the browser. Removing the #ajax
|
||||
// element turns off AJAX behaviors on that element and as a result
|
||||
// ajax.js doesn't get loaded.
|
||||
// For demonstration only! You don't need this.
|
||||
if ($no_js_use) {
|
||||
// Remove the #ajax from the above, so ajax.js won't be loaded.
|
||||
// For demonstration only.
|
||||
unset($form['next']['#ajax']);
|
||||
unset($form['prev']['#ajax']);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wizard callback function.
|
||||
*
|
||||
* @param array $form
|
||||
* Form API form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* Form API form.
|
||||
*
|
||||
* @return array
|
||||
* Form array.
|
||||
*/
|
||||
public function prompt(array $form, FormStateInterface $form_state) {
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save away the current information.
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
if ($form_state->getTriggeringElement()['#value'] == $this->t('Submit your information')) {
|
||||
$value_message = $this->t('Your information has been submitted:') . ' ';
|
||||
foreach ($form_state->getValue('value') as $step => $values) {
|
||||
$value_message .= "$step: ";
|
||||
foreach ($values as $key => $value) {
|
||||
$value_message .= "$key=$value, ";
|
||||
}
|
||||
}
|
||||
drupal_set_message($value_message);
|
||||
$form_state->setRebuild(FALSE);
|
||||
// Redirect to #action, else return.
|
||||
return;
|
||||
}
|
||||
else {
|
||||
$step = $form_state->getValue('step');
|
||||
// Increment or decrement the step as needed. Recover values if they
|
||||
// exist.
|
||||
if ($form_state->getTriggeringElement()['#value']->__toString() == $this->t('Next step')) {
|
||||
$step++;
|
||||
}
|
||||
elseif ($form_state->getTriggeringElement()['#value']->__toString() == $this->t('Previous step')) {
|
||||
$step--;
|
||||
}
|
||||
|
||||
switch ($step) {
|
||||
case 1:
|
||||
$form['step1'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Step 1: Personal details'),
|
||||
];
|
||||
$form['step1']['name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Your name'),
|
||||
'#default_value' => empty($form_state->getValue([
|
||||
'step1',
|
||||
'name',
|
||||
]) ? '' : $form_state->getValue(['step1', 'name'])),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
$form_state->setValue('step', 1);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
unset($form['step1']);
|
||||
unset($form['next']);
|
||||
$form['step2'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Step 2: Street address info'),
|
||||
];
|
||||
$form['step2']['address'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Your street address'),
|
||||
'#default_value' => empty($form_state->getValue([
|
||||
'step2',
|
||||
'address',
|
||||
]) ? '' : $form_state->getValue(['step2', 'address'])),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
$form_state->setValue('step', $step);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
|
||||
$form['step3'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Step 3: City info'),
|
||||
];
|
||||
$form['step3']['city'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Your city'),
|
||||
'#default_value' => empty($form_state->getValue([
|
||||
'step3',
|
||||
'city',
|
||||
]) ? '' : $form_state->getValue(['step3', 'city'])),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
$form_state->setValue('step', $step);
|
||||
break;
|
||||
}
|
||||
if ($step == 3) {
|
||||
|
||||
$form['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t("Submit your information"),
|
||||
];
|
||||
}
|
||||
if ($step > 1 && !isset($form['prev'])) {
|
||||
$form['prev'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => t("Previous step"),
|
||||
// Since all info will be discarded, don't validate on 'prev'.
|
||||
'#limit_validation_errors' => [],
|
||||
// #submit is required to use #limit_validation_errors.
|
||||
'#submit' => ['ajax_example_wizard_submit'],
|
||||
'#ajax' => [
|
||||
'wrapper' => 'ajax-example-wizard',
|
||||
'callback' => '::prompt',
|
||||
],
|
||||
];
|
||||
}
|
||||
if ($step < 3 && !isset($form['next'])) {
|
||||
$form['next'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Next step'),
|
||||
'#limit_validation_errors' => [],
|
||||
'#ajax' => [
|
||||
'wrapper' => 'ajax-example-wizard',
|
||||
'callback' => '::prompt',
|
||||
],
|
||||
];
|
||||
}
|
||||
$response = new AjaxResponse();
|
||||
$response->addCommand(new HtmlCommand('#ajax-example-wizard', $form));
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
{#
|
||||
|
||||
Description text for the Ajax Example.
|
||||
|
||||
#}
|
||||
|
||||
{% set simple_ajax_example = path('ajax_example.simplest') %}
|
||||
{% set ajax_generate_textfields = path('ajax_example.autotextfields') %}
|
||||
{% set ajax_submit = path('ajax_example.submit_driven_ajax') %}
|
||||
{% set ajax_dependent_dropdown = path('ajax_example.dependent_dropdown') %}
|
||||
{% set ajax_dependent_dropdown_nojs = path('ajax_example.dependent_dropdown', {'nojs': 'nojs'}) %}
|
||||
{% set ajax_dynamic_form = path('ajax_example.dynamic_form_sections') %}
|
||||
{% set ajax_dynamic_form_nojs = path('ajax_example.dynamic_form_sections', {'nojs': 'nojs'}) %}
|
||||
{% set ajax_wizard_example = path('ajax_example.wizard') %}
|
||||
{% set ajax_wizard_example_nojs = path('ajax_example.wizardnojs') %}
|
||||
|
||||
{% trans %}
|
||||
|
||||
<p>The AJAX example module provides many examples of AJAX including forms, links, and AJAX commands.</p>
|
||||
<p><a href={{ simple_ajax_example }}>Simplest AJAX Example</a></p>
|
||||
<p><a href={{ ajax_generate_textfields }}>Generate textfields</a></p>
|
||||
<p><a href={{ ajax_submit }}>Submit-driven AJAX</a></p>
|
||||
<p><a href={{ ajax_dependent_dropdown }}>Dependent dropdown</a></p>
|
||||
<p><a href={{ ajax_dependent_dropdown_nojs }}>Dependent dropdown w/ no JS</a></p>
|
||||
<p><a href={{ ajax_dynamic_form }}>Dynamic form sections</a></p>
|
||||
<p><a href={{ ajax_dynamic_form_nojs }}>Dynamic form sections w/ no JS</a></p>
|
||||
<p><a href={{ ajax_wizard_example }}>AJAX Wizard Example</a></p>
|
||||
<p><a href={{ ajax_wizard_example_nojs }}>AJAX Wizard Example w/JS turned off</a></p>
|
||||
{% endtrans %}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\ajax_example\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Verify tool block menu items and operability of all our routes.
|
||||
*
|
||||
* @group ajax_example
|
||||
* @group examples
|
||||
*
|
||||
* @ingroup ajax_example
|
||||
*/
|
||||
class AjaxExampleMenuTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['ajax_example'];
|
||||
|
||||
/**
|
||||
* The installation profile to use with this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $profile = 'minimal';
|
||||
|
||||
/**
|
||||
* Tests links.
|
||||
*/
|
||||
public function testAjaxExampleLinks() {
|
||||
// Login a user that can access content.
|
||||
$this->drupalLogin(
|
||||
$this->createUser(['access content', 'access user profiles'])
|
||||
);
|
||||
|
||||
$assertion = $this->assertSession();
|
||||
|
||||
// Routes with menu links, and their form buttons.
|
||||
$routes_with_menu_links = [
|
||||
'ajax_example.description' => [],
|
||||
'ajax_example.simplest' => [],
|
||||
'ajax_example.autotextfields' => ['Click Me'],
|
||||
'ajax_example.submit_driven_ajax' => ['Submit'],
|
||||
'ajax_example.dependent_dropdown' => ['Submit'],
|
||||
'ajax_example.dynamic_form_sections' => ['Choose'],
|
||||
'ajax_example.wizard' => ['Next step'],
|
||||
'ajax_example.wizardnojs' => ['Next step'],
|
||||
'ajax_example.ajax_link_render' => [],
|
||||
'ajax_example.autocomplete_user' => ['Submit'],
|
||||
];
|
||||
|
||||
// Ensure the links appear in the tools menu sidebar.
|
||||
$this->drupalGet('');
|
||||
foreach (array_keys($routes_with_menu_links) as $route) {
|
||||
$assertion->linkByHrefExists(Url::fromRoute($route)->getInternalPath());
|
||||
}
|
||||
|
||||
// All our routes with their form buttons.
|
||||
$routes = [
|
||||
'ajax_example.ajax_link_callback' => [],
|
||||
];
|
||||
|
||||
// Go to all the routes and click all the buttons.
|
||||
$routes = array_merge($routes_with_menu_links, $routes);
|
||||
foreach ($routes as $route => $buttons) {
|
||||
$url = Url::fromRoute($route);
|
||||
$this->drupalGet($url);
|
||||
$assertion->statusCodeEquals(200);
|
||||
foreach ($buttons as $button) {
|
||||
$this->drupalPostForm($url, [], $button);
|
||||
$assertion->statusCodeEquals(200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\ajax_example\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Functional test of non-AJAX dependent dropdown example.
|
||||
*
|
||||
* @group ajax_example
|
||||
* @group examples
|
||||
*
|
||||
* @ingroup ajax_example
|
||||
*
|
||||
* @see \Drupal\Tests\ajax_example\FunctionalJavascript\DependentDropdownTest
|
||||
*/
|
||||
class DependentDropdownTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['ajax_example'];
|
||||
|
||||
/**
|
||||
* Test the dependent dropdown form without AJAX.
|
||||
*/
|
||||
public function testDependentDropdown() {
|
||||
// Get the Mink stuff.
|
||||
$session = $this->getSession();
|
||||
$assert = $this->assertSession();
|
||||
$page = $session->getPage();
|
||||
|
||||
// Get a URL object for the form, specifying no JS.
|
||||
$dropdown_url = Url::fromRoute('ajax_example.dependent_dropdown', ['nojs' => 'nojs']);
|
||||
|
||||
// Get the form.
|
||||
$this->drupalGet($dropdown_url);
|
||||
// Check for the initial state.
|
||||
$assert->fieldDisabled('instrument_dropdown');
|
||||
$assert->fieldValueEquals('instrument_dropdown', 'none');
|
||||
$submit_button = $page->findButton('edit-submit');
|
||||
$this->assertTrue($submit_button->hasAttribute('disabled'));
|
||||
|
||||
// Run through the matrix of form submissions.
|
||||
$families = [
|
||||
'String' => ['Violin', 'Viola', 'Cello', 'Double Bass'],
|
||||
'Woodwind' => ['Flute', 'Clarinet', 'Oboe', 'Bassoon'],
|
||||
'Brass' => ['Trumpet', 'Trombone', 'French Horn', 'Euphonium'],
|
||||
'Percussion' => ['Bass Drum', 'Timpani', 'Snare Drum', 'Tambourine'],
|
||||
];
|
||||
|
||||
foreach ($families as $family => $instruments) {
|
||||
// Post the form for the instrument family.
|
||||
$this->drupalPostForm($dropdown_url, ['instrument_family_dropdown' => $family], 'Choose');
|
||||
// Get the instrument dropdown elements.
|
||||
$instrument_options = $page->findAll('css', '#edit-instrument-dropdown option');
|
||||
$this->assertCount(count($instruments), $instrument_options);
|
||||
// Make sure all the instruments are in the select dropdown.
|
||||
foreach ($instrument_options as $instrument) {
|
||||
$this->assertContains($instrument->getAttribute('value'), $instruments);
|
||||
}
|
||||
// Post each instrument. We have to 'choose' again in order to unlock the
|
||||
// instrument dropdown.
|
||||
foreach ($instruments as $instrument) {
|
||||
$this->drupalPostForm($dropdown_url, ['instrument_family_dropdown' => $family], 'Choose');
|
||||
$this->drupalPostForm(NULL, ['instrument_dropdown' => $instrument], 'Submit');
|
||||
$assert->pageTextContains("Your values have been submitted. Instrument family: $family, Instrument: $instrument");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\ajax_example\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Functional test of non-AJAX dependent dropdown example.
|
||||
*
|
||||
* @group ajax_example
|
||||
* @group examples
|
||||
*
|
||||
* @ingroup ajax_example
|
||||
*
|
||||
* @see Drupal\Tests\ajax_example\FunctionalJavascript\DynamicFormSectionsTest
|
||||
*/
|
||||
class DynamicFormSectionsTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['ajax_example'];
|
||||
|
||||
/**
|
||||
* Test the dynamic sections form without AJAX.
|
||||
*/
|
||||
public function testDynamicFormSections() {
|
||||
// Get the Mink stuff.
|
||||
$assert = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Get a URL object for the form, specifying no JS.
|
||||
$dropdown_url = Url::fromRoute('ajax_example.dynamic_form_sections', ['nojs' => 'nojs']);
|
||||
|
||||
// Get the form.
|
||||
$this->drupalGet($dropdown_url);
|
||||
// Check for the initial state.
|
||||
$detail_children = $page->findAll('css', 'div.details-wrapper *');
|
||||
$this->assertEmpty($detail_children);
|
||||
|
||||
// Go through the dropdown options. First outlier is 'Choose question style'
|
||||
// which should have an empty details section.
|
||||
$this->drupalPostForm($dropdown_url, ['question_type_select' => 'Choose question style'], 'Choose');
|
||||
$detail_children = $page->findAll('css', 'div.details-wrapper *');
|
||||
$this->assertEmpty($detail_children);
|
||||
|
||||
// Cycle through the other dropdown values.
|
||||
$question_styles = [
|
||||
'Multiple Choice',
|
||||
'True/False',
|
||||
'Fill-in-the-blanks',
|
||||
];
|
||||
// These all add stuff to the details wrapper.
|
||||
foreach ($question_styles as $question_style) {
|
||||
$this->drupalPostForm($dropdown_url, ['question_type_select' => $question_style], 'Choose');
|
||||
$detail_children = $page->findAll('css', 'div.details-wrapper *');
|
||||
$this->assertNotEmpty($detail_children);
|
||||
$this->drupalPostForm(NULL, ['question' => 'George Washington'], 'Submit your answer');
|
||||
$assert->pageTextContains('You got the right answer: George Washington');
|
||||
}
|
||||
// One wrong answer to exercise that code path.
|
||||
$this->drupalPostForm($dropdown_url, ['question_type_select' => 'Multiple Choice'], 'Choose');
|
||||
$detail_children = $page->findAll('css', 'div.details-wrapper *');
|
||||
$this->assertNotEmpty($detail_children);
|
||||
$this->drupalPostForm(NULL, ['question' => 'Abraham Lincoln'], 'Submit your answer');
|
||||
$assert->pageTextContains('Sorry, your answer (Abraham Lincoln) is wrong');
|
||||
}
|
||||
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\ajax_example\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
|
||||
|
||||
/**
|
||||
* Test the user interactions for the Autotextfields example.
|
||||
*
|
||||
* @group ajax_example
|
||||
*/
|
||||
class AutofieldsTest extends JavascriptTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['ajax_example'];
|
||||
|
||||
/**
|
||||
* Test the user interactions for the Autotextfields example.
|
||||
*/
|
||||
public function testAutotextfields() {
|
||||
// Get our Mink stuff.
|
||||
$session = $this->getSession();
|
||||
$page = $session->getPage();
|
||||
$assert = $this->assertSession();
|
||||
|
||||
// Get the page.
|
||||
$form_url = Url::fromRoute('ajax_example.autotextfields');
|
||||
$this->drupalGet($form_url);
|
||||
|
||||
// Check our initial state.
|
||||
$assert->checkboxNotChecked('ask_first_name');
|
||||
$assert->checkboxNotChecked('ask_last_name');
|
||||
$assert->fieldNotExists('first_name');
|
||||
$assert->fieldNotExists('last_name');
|
||||
// Submit the form. This tests what happens when there are no user
|
||||
// interactions because drupalPostForm() reloads the form.
|
||||
$this->drupalPostForm($form_url, [], 'Click Me');
|
||||
$assert->pageTextContains('Submit handler: First name: n/a Last name: n/a');
|
||||
|
||||
// Ask for the first name.
|
||||
$page->checkField('ask_first_name');
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
$assert->fieldExists('first_name');
|
||||
$assert->fieldNotExists('last_name');
|
||||
// Submit the form. We have to find the field and set its value rather than
|
||||
// use drupalPostForm(), because when we post the form, it will be rebuilt.
|
||||
// We are testing the form state after AJAX has modified it, so we must
|
||||
// preserve that.
|
||||
$page->findField('first_name')->setValue('Dries');
|
||||
$page->pressButton('Click Me');
|
||||
$assert->pageTextContains('Submit handler: First name: Dries Last name: n/a');
|
||||
|
||||
// Ask for the first and last name.
|
||||
$page->checkField('ask_first_name');
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
$assert->fieldExists('first_name');
|
||||
$page->checkField('ask_last_name');
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
$assert->fieldExists('last_name');
|
||||
// Submit the form.
|
||||
$page->findField('first_name')->setValue('Dries');
|
||||
$page->findField('last_name')->setValue('Buytaert');
|
||||
$page->pressButton('Click Me');
|
||||
$assert->pageTextContains('Submit handler: First name: Dries Last name: Buytaert');
|
||||
|
||||
// Ask for only the last name.
|
||||
$page->checkField('ask_last_name');
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
$assert->fieldNotExists('first_name');
|
||||
$assert->fieldExists('last_name');
|
||||
// Submit the form.
|
||||
$page->findField('last_name')->setValue('Buytaert');
|
||||
$page->pressButton('Click Me');
|
||||
$assert->pageTextContains('Submit handler: First name: n/a Last name: Buytaert');
|
||||
}
|
||||
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\ajax_example\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
|
||||
|
||||
/**
|
||||
* Functional test of dependent dropdown example.
|
||||
*
|
||||
* @group ajax_example
|
||||
*
|
||||
* @see \Drupal\Tests\ajax_example\FunctionalJavascript\DependentDropdownTest
|
||||
*/
|
||||
class DependentDropdownTest extends JavascriptTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['ajax_example'];
|
||||
|
||||
/**
|
||||
* Test the dependent dropdown form with AJAX.
|
||||
*/
|
||||
public function testDependentDropdown() {
|
||||
// Get the Mink stuff.
|
||||
$assert = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Get a URL object for the form, specifying AJAX.
|
||||
$dropdown_url = Url::fromRoute('ajax_example.dependent_dropdown', ['nojs' => 'ajax']);
|
||||
|
||||
// Get the form.
|
||||
$this->drupalGet($dropdown_url);
|
||||
// Check for the initial state.
|
||||
$assert->fieldDisabled('instrument_dropdown');
|
||||
$assert->fieldValueEquals('instrument_dropdown', 'none');
|
||||
$submit_button = $page->findButton('edit-submit');
|
||||
$this->assertTrue($submit_button->hasAttribute('disabled'));
|
||||
|
||||
// Run through the matrix of families.
|
||||
$families = [
|
||||
'String' => ['Violin', 'Viola', 'Cello', 'Double Bass'],
|
||||
'Woodwind' => ['Flute', 'Clarinet', 'Oboe', 'Bassoon'],
|
||||
'Brass' => ['Trumpet', 'Trombone', 'French Horn', 'Euphonium'],
|
||||
'Percussion' => ['Bass Drum', 'Timpani', 'Snare Drum', 'Tambourine'],
|
||||
];
|
||||
|
||||
foreach ($families as $family => $instruments) {
|
||||
// Select a family.
|
||||
$family_dropdown = $assert->fieldExists('instrument_family_dropdown');
|
||||
$family_dropdown->setValue($family);
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
|
||||
// Get the instrument dropdown elements.
|
||||
$instrument_options = $page->findAll('css', 'select[name="instrument_dropdown"] option');
|
||||
$this->assertCount(count($instruments), $instrument_options);
|
||||
// Make sure all the instruments are in the select dropdown.
|
||||
foreach ($instrument_options as $instrument) {
|
||||
$this->assertContains($instrument->getAttribute('value'), $instruments);
|
||||
}
|
||||
|
||||
// Post each instrument.
|
||||
foreach ($instruments as $instrument) {
|
||||
$this->drupalGet($dropdown_url);
|
||||
$family_dropdown->setValue($family);
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
$this->drupalPostForm(NULL, ['instrument_dropdown' => $instrument], 'Submit');
|
||||
$assert->pageTextContains("Your values have been submitted. Instrument family: $family, Instrument: $instrument");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\ajax_example\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
|
||||
|
||||
/**
|
||||
* Functional test of dependent dropdown example.
|
||||
*
|
||||
* @group ajax_example
|
||||
*
|
||||
* @see Drupal\Tests\ajax_example\Functional\DynamicFormSectionsTest
|
||||
*/
|
||||
class DynamicFormSectionsTest extends JavascriptTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['ajax_example'];
|
||||
|
||||
/**
|
||||
* Test the dependent dropdown form with AJAX.
|
||||
*/
|
||||
public function testDynamicFormSections() {
|
||||
// Get the Mink stuff.
|
||||
$assert = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Get a URL object for the form, specifying no JS.
|
||||
$dropdown_url = Url::fromRoute('ajax_example.dynamic_form_sections', ['nojs' => 'ajax']);
|
||||
|
||||
// Get the form.
|
||||
$this->drupalGet($dropdown_url);
|
||||
// Check for the initial state.
|
||||
$this->assertEmpty($page->findAll('css', 'div.details-wrapper *'));
|
||||
|
||||
// Cycle through the other dropdown values.
|
||||
$question_styles = [
|
||||
'Multiple Choice',
|
||||
'True/False',
|
||||
'Fill-in-the-blanks',
|
||||
];
|
||||
|
||||
// Check expectations against the details wrapper.
|
||||
$question_type_dropdown = $page->findField('question_type_select');
|
||||
foreach ($question_styles as $question_style) {
|
||||
$question_type_dropdown->setValue($question_style);
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
$this->assertNotEmpty($page->findAll('css', 'div.details-wrapper *'));
|
||||
}
|
||||
// Prompt to choose question should remove the question.
|
||||
$question_type_dropdown->setValue('Choose question style');
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
$this->assertEmpty($page->findAll('css', 'div.details-wrapper *'));
|
||||
|
||||
// Submit the correct answers.
|
||||
foreach ($question_styles as $question_style) {
|
||||
$this->drupalGet($dropdown_url);
|
||||
$question_type_dropdown->setValue($question_style);
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
$this->drupalPostForm(NULL, ['question' => 'George Washington'], 'Submit your answer');
|
||||
$assert->pageTextContains('You got the right answer: George Washington');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\ajax_example\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
|
||||
|
||||
/**
|
||||
* Tests the behavior of the entity_autocomplete example.
|
||||
*
|
||||
* @group ajax_example
|
||||
*/
|
||||
class EntityAutocompleteTest extends JavascriptTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['ajax_example'];
|
||||
|
||||
/**
|
||||
* Test the behavior of the submit-driven AJAX example.
|
||||
*
|
||||
* Behaviors to test:
|
||||
* - GET the route ajax_example.autocomplete_user.
|
||||
* - Examine the DOM to make sure our change hasn't happened yet.
|
||||
* - Send an event to the DOM to trigger the autocomplete.
|
||||
* - Wait for the autocomplete request to complete.
|
||||
* - Examine the DOM to see if our expected change happened.
|
||||
* - Submit some names to see if our form processed the user properly.
|
||||
*/
|
||||
public function testSubmitDriven() {
|
||||
// Set up some accounts with known names.
|
||||
$names = ['bb', 'bc'];
|
||||
foreach ($names as $name) {
|
||||
$this->createUser([], $name);
|
||||
}
|
||||
|
||||
// Get our various Mink elements.
|
||||
$assert = $this->assertSession();
|
||||
$session = $this->getSession();
|
||||
$page = $session->getPage();
|
||||
// We'll be using the users field quite a bit, so let's make it a variable.
|
||||
$users_field_name = 'edit-users';
|
||||
|
||||
// Get the form.
|
||||
$this->drupalGet(Url::fromRoute('ajax_example.autocomplete_user'));
|
||||
// Examine the DOM to make sure our change hasn't happened yet.
|
||||
$assert->fieldValueEquals($users_field_name, '');
|
||||
|
||||
// Send an event to the DOM. This will start the autocomplete process.
|
||||
$autocomplete_field = $page->findField($users_field_name);
|
||||
$session->getDriver()->keyDown($autocomplete_field->getXpath(), 'b');
|
||||
|
||||
// Wait for the autocomplete request to complete.
|
||||
$assert->waitOnAutocomplete();
|
||||
|
||||
// Examine the DOM to see if our expected change happened.
|
||||
$results = $page->findAll('css', '.ui-autocomplete li');
|
||||
$this->assertCount(2, $results);
|
||||
foreach ($results as $result) {
|
||||
$this->assertContains($result->getText(), $names);
|
||||
}
|
||||
|
||||
// Submit to see if our form processed the user properly.
|
||||
$this->submitForm([$users_field_name => 'bb, bc'], 'Submit');
|
||||
$assert->pageTextContains('These are your users: bb bc');
|
||||
}
|
||||
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\ajax_example\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
|
||||
|
||||
/**
|
||||
* Test the user interactions for the Simplest example.
|
||||
*
|
||||
* @group ajax_example
|
||||
*/
|
||||
class SimplestTest extends JavascriptTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['ajax_example'];
|
||||
|
||||
/**
|
||||
* Test the user interactions for the Autotextfields example.
|
||||
*/
|
||||
public function testAutotextfields() {
|
||||
// Get our Mink stuff.
|
||||
$session = $this->getSession();
|
||||
$page = $session->getPage();
|
||||
$assert = $this->assertSession();
|
||||
|
||||
// Get the page.
|
||||
$form_url = Url::fromRoute('ajax_example.simplest');
|
||||
$this->drupalGet($form_url);
|
||||
|
||||
// Don't repeat ourselves. This makes it easier if we change the markup
|
||||
// later.
|
||||
$description_selector = '#replace-textfield-container div.description';
|
||||
|
||||
// Check our initial state.
|
||||
$assert->elementExists('css', '#replace-textfield-container');
|
||||
$assert->elementNotExists('css', $description_selector);
|
||||
|
||||
// Select values on the dropdown. Start with three so the change event is
|
||||
// triggered.
|
||||
foreach (['three', 'two', 'one'] as $value) {
|
||||
// Select the dropdown value.
|
||||
$page->selectFieldOption('changethis', $value);
|
||||
// Wait for AJAX to happen.
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
// Assert that the description exists.
|
||||
$assert->elementExists('css', $description_selector);
|
||||
// Get the description element from the page.
|
||||
$prompt_element = $page->find('css', $description_selector);
|
||||
// Assert that the description element says what we expect it to say.
|
||||
$this->assertEquals(
|
||||
"Say why you chose '$value'",
|
||||
$prompt_element->getText()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\ajax_example\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
|
||||
|
||||
/**
|
||||
* Tests the behavior of the submit-driven AJAX example.
|
||||
*
|
||||
* @group ajax_example
|
||||
*/
|
||||
class SubmitDrivenTest extends JavascriptTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['ajax_example'];
|
||||
|
||||
/**
|
||||
* Test the behavior of the submit-driven AJAX example.
|
||||
*
|
||||
* Behaviors to test:
|
||||
* - GET the route ajax_example.submit_driven_ajax.
|
||||
* - Examine the DOM to make sure our change hasn't happened yet.
|
||||
* - Submit the form.
|
||||
* - Wait for the AJAX request to complete.
|
||||
* - Examine the DOM to see if our expected change happened.
|
||||
*/
|
||||
public function testSubmitDriven() {
|
||||
// Get the session assertion object.
|
||||
$assert = $this->assertSession();
|
||||
// Get the page.
|
||||
$this->drupalGet(Url::fromRoute('ajax_example.submit_driven_ajax'));
|
||||
// Examine the DOM to make sure our change hasn't happened yet.
|
||||
$assert->pageTextNotContains('Clicked submit (Submit):');
|
||||
// Submit the form.
|
||||
$this->submitForm([], 'Submit');
|
||||
// Wait on the AJAX request.
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
// Compare DOM to our expectations.
|
||||
$assert->pageTextContains('Clicked submit (Submit):');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
name: Batch Example
|
||||
type: module
|
||||
description: An example outlining how a module can define batch operations.
|
||||
package: Example modules
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- examples
|
||||
- toolbar
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.x-dev'
|
||||
core: '8.x'
|
||||
project: 'examples'
|
||||
datestamp: 1513537386
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update, and uninstall functions for the batch_example module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Example of batch-driven update function.
|
||||
*
|
||||
* Because some update functions may require the batch API, the $sandbox
|
||||
* provides a place to store state. When $sandbox['#finished'] == TRUE,
|
||||
* calls to this update function are completed.
|
||||
*
|
||||
* The $sandbox param provides a way to store data during multiple invocations.
|
||||
* When the $sandbox['#finished'] == 1, execution is complete.
|
||||
*
|
||||
* This dummy 'update' function changes no state in the system. It simply
|
||||
* loads each node.
|
||||
*
|
||||
* To make this update function run again and again, execute the query
|
||||
* "update system set schema_version = 0 where name = 'batch_example';"
|
||||
* and then run /update.php.
|
||||
*
|
||||
* @ingroup batch_example
|
||||
*/
|
||||
function batch_example_update_8001(&$sandbox) {
|
||||
// Use the sandbox at your convenience to store the information needed
|
||||
// to track progression between successive calls to the function.
|
||||
if (!isset($sandbox['progress'])) {
|
||||
// The count of nodes visited so far.
|
||||
$sandbox['progress'] = 0;
|
||||
// Total nodes that must be visited.
|
||||
$sandbox['max'] = db_query('SELECT COUNT(nid) FROM {node}')->fetchField();
|
||||
// A place to store messages during the run.
|
||||
$sandbox['messages'] = [];
|
||||
// Last node read via the query.
|
||||
$sandbox['current_node'] = -1;
|
||||
}
|
||||
|
||||
// Process nodes by groups of 10 (arbitrary value).
|
||||
// When a group is processed, the batch update engine determines
|
||||
// whether it should continue processing in the same request or provide
|
||||
// progress feedback to the user and wait for the next request.
|
||||
$limit = 10;
|
||||
|
||||
// Retrieve the next group of nids.
|
||||
$query = db_select('node', 'n');
|
||||
$query->fields('n', ['nid']);
|
||||
$result = $query
|
||||
->where('n.nid > :nid', [':nid' => $sandbox['current_node']])
|
||||
->range(0, $limit)
|
||||
->orderBy('n.nid', 'ASC')
|
||||
->execute();
|
||||
foreach ($result as $row) {
|
||||
// Here we actually perform a dummy 'update' on the current node.
|
||||
$node = db_query('SELECT nid FROM {node} WHERE nid = :nid', [':nid' => $row->nid])->fetchField();
|
||||
|
||||
// Update our progress information.
|
||||
$sandbox['progress']++;
|
||||
$sandbox['current_node'] = $row->nid;
|
||||
}
|
||||
|
||||
// Set the "finished" status, to tell batch engine whether this function
|
||||
// needs to run again. If you set a float, this will indicate the progress
|
||||
// of the batch so the progress bar will update.
|
||||
$sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['max']) ? TRUE : ($sandbox['progress'] / $sandbox['max']);
|
||||
|
||||
// Set up a per-run message; Make a copy of $sandbox so we can change it.
|
||||
// This is simply a debugging stanza to illustrate how to capture status
|
||||
// from each pass through hook_update_N().
|
||||
$sandbox_status = $sandbox;
|
||||
// Don't want them in the output.
|
||||
unset($sandbox_status['messages']);
|
||||
$sandbox['messages'][] = t('$sandbox=') . print_r($sandbox_status, TRUE);
|
||||
|
||||
if ($sandbox['#finished']) {
|
||||
// hook_update_N() may optionally return a string which will be displayed
|
||||
// to the user.
|
||||
$final_message = '<ul><li>' . implode('</li><li>', $sandbox['messages']) . "</li></ul>";
|
||||
return t('The batch_example demonstration update did what it was supposed to do: @message', ['@message' => $final_message]);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
# Define default links for this module.
|
||||
batch_example.form:
|
||||
title: Batch API Examples
|
||||
description: Batch examples using Drupal Batch API.
|
||||
route_name: batch_example.form
|
||||
@@ -1,133 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Outlines how a module can use the Batch API.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup batch_example Example: Batch API
|
||||
* @ingroup examples
|
||||
* @{
|
||||
* Outlines how a module can use the Batch API.
|
||||
*
|
||||
* Batches allow heavy processing to be spread out over several page
|
||||
* requests, ensuring that the processing does not get interrupted
|
||||
* because of a PHP timeout, while allowing the user to receive feedback
|
||||
* on the progress of the ongoing operations. It also can reduce out of memory
|
||||
* situations.
|
||||
*
|
||||
* The @link batch_example.install .install file @endlink also shows how the
|
||||
* Batch API can be used to handle long-running hook_update_N() functions.
|
||||
*
|
||||
* Two harmless batches are defined:
|
||||
* - batch 1: Load the node with the lowest nid 100 times.
|
||||
* - batch 2: Load all nodes, 20 times and uses a progressive op, loading nodes
|
||||
* by groups of 5.
|
||||
*
|
||||
* @see batch
|
||||
*/
|
||||
|
||||
/**
|
||||
* Batch operation for batch 1: one at a time.
|
||||
*
|
||||
* This is the function that is called on each operation in batch 1.
|
||||
*/
|
||||
function batch_example_op_1($id, $operation_details, &$context) {
|
||||
// Simulate long process by waiting 1/50th of a second.
|
||||
usleep(20000);
|
||||
|
||||
// Store some results for post-processing in the 'finished' callback.
|
||||
// The contents of 'results' will be available as $results in the
|
||||
// 'finished' function (in this example, batch_example_finished()).
|
||||
$context['results'][] = $id;
|
||||
|
||||
// Optional message displayed under the progressbar.
|
||||
$context['message'] = t('Running Batch "@id" @details',
|
||||
['@id' => $id, '@details' => $operation_details]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch operation for batch 2: five at a time.
|
||||
*
|
||||
* This is the function that is called on each operation in batch 2.
|
||||
*
|
||||
* After each group of 5 control is returned to the batch API for later
|
||||
* continuation.
|
||||
*/
|
||||
function batch_example_op_2($operation_details, &$context) {
|
||||
// Use the $context['sandbox'] at your convenience to store the
|
||||
// information needed to track progression between successive calls.
|
||||
if (empty($context['sandbox'])) {
|
||||
$context['sandbox'] = [];
|
||||
$context['sandbox']['progress'] = 0;
|
||||
$context['sandbox']['current_node'] = 0;
|
||||
|
||||
// Save node count for the termination message.
|
||||
$context['sandbox']['max'] = 30;
|
||||
}
|
||||
|
||||
// Process in groups of 5 (arbitrary value).
|
||||
// When a group of five is processed, the batch update engine determines
|
||||
// whether it should continue processing in the same request or provide
|
||||
// progress feedback to the user and wait for the next request.
|
||||
// That way even though we're already processing at the operation level
|
||||
// the operation itself is interruptible.
|
||||
$limit = 5;
|
||||
|
||||
// Retrieve the next group.
|
||||
$result = range($context['sandbox']['current_node'] + 1, $context['sandbox']['current_node'] + 1 + $limit);
|
||||
|
||||
foreach ($result as $row) {
|
||||
// Here we actually perform our dummy 'processing' on the current node.
|
||||
usleep(20000);
|
||||
|
||||
// Store some results for post-processing in the 'finished' callback.
|
||||
// The contents of 'results' will be available as $results in the
|
||||
// 'finished' function (in this example, batch_example_finished()).
|
||||
$context['results'][] = $row . ' ' . $operation_details;
|
||||
|
||||
// Update our progress information.
|
||||
$context['sandbox']['progress']++;
|
||||
$context['sandbox']['current_node'] = $row;
|
||||
$context['message'] = t('Running Batch "@id" @details',
|
||||
['@id' => $row, '@details' => $operation_details]
|
||||
);
|
||||
}
|
||||
|
||||
// Inform the batch engine that we are not finished,
|
||||
// and provide an estimation of the completion level we reached.
|
||||
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
|
||||
$context['finished'] = ($context['sandbox']['progress'] >= $context['sandbox']['max']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch 'finished' callback used by both batch 1 and batch 2.
|
||||
*/
|
||||
function batch_example_finished($success, $results, $operations) {
|
||||
if ($success) {
|
||||
// Here we could do something meaningful with the results.
|
||||
// We just display the number of nodes we processed...
|
||||
drupal_set_message(t('@count results processed.', ['@count' => count($results)]));
|
||||
drupal_set_message(t('The final result was "%final"', ['%final' => end($results)]));
|
||||
}
|
||||
else {
|
||||
// An error occurred.
|
||||
// $operations contains the operations that remained unprocessed.
|
||||
$error_operation = reset($operations);
|
||||
drupal_set_message(
|
||||
t('An error occurred while processing @operation with arguments : @args',
|
||||
[
|
||||
'@operation' => $error_operation[0],
|
||||
'@args' => print_r($error_operation[0], TRUE),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup batch_example".
|
||||
*/
|
||||
@@ -1,7 +0,0 @@
|
||||
batch_example.form:
|
||||
path: 'examples/batch_example'
|
||||
defaults:
|
||||
_form: '\Drupal\batch_example\Form\BatchExampleForm'
|
||||
_title: 'Demo of batch processing'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
@@ -1,148 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\batch_example\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Form with examples on how to use cache.
|
||||
*/
|
||||
class BatchExampleForm extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'batch_example_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
|
||||
$form['description'] = [
|
||||
'#type' => 'markup',
|
||||
'#markup' => t('This example offers two different batches. The first does 1000 identical operations, each completed in on run; the second does 20 operations, but each takes more than one run to operate if there are more than 5 nodes.'),
|
||||
];
|
||||
$form['batch'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => 'Choose batch',
|
||||
'#options' => [
|
||||
'batch_1' => t('batch 1 - 1000 operations'),
|
||||
'batch_2' => t('batch 2 - 20 operations.'),
|
||||
],
|
||||
];
|
||||
$form['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => 'Go',
|
||||
];
|
||||
|
||||
return $form;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Gather our form value.
|
||||
$value = $form_state->getValues()['batch'];
|
||||
// Set the batch, using convenience methods.
|
||||
$batch = [];
|
||||
switch ($value) {
|
||||
case 'batch_1':
|
||||
$batch = $this->generateBatch1();
|
||||
break;
|
||||
|
||||
case 'batch_2':
|
||||
$batch = $this->generateBatch2();
|
||||
break;
|
||||
}
|
||||
batch_set($batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Batch 1.
|
||||
*
|
||||
* Batch 1 will process one item at a time.
|
||||
*
|
||||
* This creates an operations array defining what batch 1 should do, including
|
||||
* what it should do when it's finished. In this case, each operation is the
|
||||
* same and by chance even has the same $nid to operate on, but we could have
|
||||
* a mix of different types of operations in the operations array.
|
||||
*/
|
||||
public function generateBatch1() {
|
||||
$num_operations = 1000;
|
||||
drupal_set_message(t('Creating an array of @num operations', ['@num' => $num_operations]));
|
||||
|
||||
$operations = [];
|
||||
// Set up an operations array with 1000 elements, each doing function
|
||||
// batch_example_op_1.
|
||||
// Each operation in the operations array means at least one new HTTP
|
||||
// request, running Drupal from scratch to accomplish the operation. If the
|
||||
// operation returns with $context['finished'] != TRUE, then it will be
|
||||
// called again.
|
||||
// In this example, $context['finished'] is always TRUE.
|
||||
for ($i = 0; $i < $num_operations; $i++) {
|
||||
// Each operation is an array consisting of
|
||||
// - The function to call.
|
||||
// - An array of arguments to that function.
|
||||
$operations[] = [
|
||||
'batch_example_op_1',
|
||||
[
|
||||
$i + 1,
|
||||
t('(Operation @operation)', ['@operation' => $i]),
|
||||
],
|
||||
];
|
||||
}
|
||||
$batch = [
|
||||
'title' => t('Creating an array of @num operations', ['@num' => $num_operations]),
|
||||
'operations' => $operations,
|
||||
'finished' => 'batch_example_finished',
|
||||
];
|
||||
return $batch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Batch 2.
|
||||
*
|
||||
* Batch 2 will process five items at a time.
|
||||
*
|
||||
* This creates an operations array defining what batch 2 should do, including
|
||||
* what it should do when it's finished. In this case, each operation is the
|
||||
* same and by chance even has the same $nid to operate on, but we could have
|
||||
* a mix of different types of operations in the operations array.
|
||||
*/
|
||||
public function generateBatch2() {
|
||||
$num_operations = 20;
|
||||
|
||||
$operations = [];
|
||||
// 20 operations, each one loads all nodes.
|
||||
for ($i = 0; $i < $num_operations; $i++) {
|
||||
$operations[] = [
|
||||
'batch_example_op_2',
|
||||
[t('(Operation @operation)', ['@operation' => $i])],
|
||||
];
|
||||
}
|
||||
$batch = [
|
||||
'operations' => $operations,
|
||||
'finished' => 'batch_example_finished',
|
||||
// @current, @remaining, @total, @percentage, @estimate and @elapsed.
|
||||
// These placeholders are replaced with actual values in _batch_process(),
|
||||
// using strtr() instead of t(). The values are determined based on the
|
||||
// number of operations in the 'operations' array (above), NOT by the
|
||||
// number of nodes that will be processed. In this example, there are 20
|
||||
// operations, so @total will always be 20, even though there are multiple
|
||||
// nodes per operation.
|
||||
// Defaults to t('Completed @current of @total.').
|
||||
'title' => t('Processing batch 2'),
|
||||
'init_message' => t('Batch 2 is starting.'),
|
||||
'progress_message' => t('Processed @current out of @total.'),
|
||||
'error_message' => t('Batch 2 has encountered an error.'),
|
||||
];
|
||||
return $batch;
|
||||
}
|
||||
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\batch_example\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Functional tests for the Batch Example module.
|
||||
*
|
||||
* @group fapi_example
|
||||
*
|
||||
* @ingroup batch_example
|
||||
*/
|
||||
class BatchExampleWebTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
static public $modules = ['node', 'batch_example'];
|
||||
|
||||
/**
|
||||
* Login user and test both batch examples.
|
||||
*/
|
||||
public function testBatchExampleBasic() {
|
||||
// Login the admin user.
|
||||
$web_user = $this->drupalCreateUser(['access content']);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Launch Batch 1.
|
||||
$this->drupalPostForm('examples/batch_example', ['batch' => 'batch_1'], 'Go');
|
||||
// Check that 1000 operations were performed.
|
||||
$this->assertText('1000 results processed');
|
||||
|
||||
// Launch Batch 2.
|
||||
$this->drupalPostForm('examples/batch_example', ['batch' => 'batch_2'], 'Go');
|
||||
// Check that 600 operations were performed.
|
||||
$this->assertText('600 results processed');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
name: Block Example
|
||||
type: module
|
||||
description: Demonstrates how a module can define blocks.
|
||||
package: Example modules
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- drupal:block
|
||||
- drupal:node
|
||||
- examples:examples
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.x-dev'
|
||||
core: '8.x'
|
||||
project: 'examples'
|
||||
datestamp: 1513537386
|
||||
@@ -1,3 +0,0 @@
|
||||
block_example.description:
|
||||
title: Block Example
|
||||
route_name: block_example.description
|
||||
@@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Module file for block_example.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Block\BlockPluginInterface;
|
||||
|
||||
/**
|
||||
* @defgroup block_example Example: Block
|
||||
* @ingroup examples
|
||||
* @{
|
||||
* Demonstrates code creation of blocks.
|
||||
*
|
||||
* This example demonstrates how a module can define blocks that can be
|
||||
* displayed on various pages of a site, and how to alter blocks provided by
|
||||
* other modules.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_block_view_alter().
|
||||
*
|
||||
* This hook allows you to modify the output of any block in the system.
|
||||
*
|
||||
* We are going to change the block label to uppercase if it contains the string
|
||||
* "uppercase" or if the default block label contains this string. The default
|
||||
* block label is set programmatically in the subject key of the block's plugin
|
||||
* definition. The configurable block label, which can be overridden through the
|
||||
* UI, is found in the "#configuration" key of the block's build definition.
|
||||
* This module creates a block that demonstrates the uppercase effect in the
|
||||
* "Example: uppercase this please" block. You can also demonstrate the effect
|
||||
* of this hook by editing the title of an existing block or by creating a new
|
||||
* block which where the default label has the string "uppercase" in it.
|
||||
*
|
||||
* Instead of hook_block_view_alter(), which is called for all blocks, you can
|
||||
* also use hook_block_view_BASE_BLOCK_ID_alter() to alter a specific block. To
|
||||
* only change the "example_uppercase" block we would use the function:
|
||||
* hook_block_view_example_uppercase_alter().
|
||||
*/
|
||||
function block_example_block_view_alter(array &$build, BlockPluginInterface $block) {
|
||||
// We'll search for the string 'uppercase'.
|
||||
$definition = $block->getPluginDefinition();
|
||||
if ((!empty($build['#configuration']['label']) && stristr($build['#configuration']['label'], 'uppercase')) || (!empty($definition['subject']) && stristr($definition['subject'], 'uppercase'))) {
|
||||
// This will uppercase the block title.
|
||||
$build['#configuration']['label'] = Unicode::strtoupper($build['#configuration']['label']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup block_example".
|
||||
*/
|
||||
@@ -1,7 +0,0 @@
|
||||
block_example.description:
|
||||
path: '/examples/block-example'
|
||||
defaults:
|
||||
_controller: '\Drupal\block_example\Controller\BlockExampleController::description'
|
||||
_title: 'Block Example'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
block.settings.example_configurable_text:
|
||||
type: block_settings
|
||||
label: 'Example configurable text block configuration'
|
||||
mapping:
|
||||
block_example_string:
|
||||
type: text
|
||||
label: 'Block contents'
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_example\Controller;
|
||||
|
||||
use Drupal\examples\Utility\DescriptionTemplateTrait;
|
||||
/**
|
||||
* Controller routines for block example routes.
|
||||
*/
|
||||
class BlockExampleController {
|
||||
use DescriptionTemplateTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getModuleName() {
|
||||
return 'block_example';
|
||||
}
|
||||
|
||||
}
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_example\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a 'Example: configurable text string' block.
|
||||
*
|
||||
* Drupal\Core\Block\BlockBase gives us a very useful set of basic functionality
|
||||
* for this configurable block. We can just fill in a few of the blanks with
|
||||
* defaultConfiguration(), blockForm(), blockSubmit(), and build().
|
||||
*
|
||||
* @Block(
|
||||
* id = "example_configurable_text",
|
||||
* admin_label = @Translation("Example: configurable text")
|
||||
* )
|
||||
*/
|
||||
class ExampleConfigurableTextBlock extends BlockBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* This method sets the block default configuration. This configuration
|
||||
* determines the block's behavior when a block is initially placed in a
|
||||
* region. Default values for the block configuration form should be added to
|
||||
* the configuration array. System default configurations are assembled in
|
||||
* BlockBase::__construct() e.g. cache setting and block title visibility.
|
||||
*
|
||||
* @see \Drupal\block\BlockBase::__construct()
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'block_example_string' => $this->t('A default value. This block was created at %time', ['%time' => date('c')]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* This method defines form elements for custom block configuration. Standard
|
||||
* block configuration fields are added by BlockBase::buildConfigurationForm()
|
||||
* (block title and title visibility) and BlockFormController::form() (block
|
||||
* visibility settings).
|
||||
*
|
||||
* @see \Drupal\block\BlockBase::buildConfigurationForm()
|
||||
* @see \Drupal\block\BlockFormController::form()
|
||||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state) {
|
||||
$form['block_example_string_text'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Block contents'),
|
||||
'#description' => $this->t('This text will appear in the example block.'),
|
||||
'#default_value' => $this->configuration['block_example_string'],
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* This method processes the blockForm() form fields when the block
|
||||
* configuration form is submitted.
|
||||
*
|
||||
* The blockValidate() method can be used to validate the form submission.
|
||||
*/
|
||||
public function blockSubmit($form, FormStateInterface $form_state) {
|
||||
$this->configuration['block_example_string']
|
||||
= $form_state->getValue('block_example_string_text');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
return [
|
||||
'#markup' => $this->configuration['block_example_string'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_example\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
|
||||
/**
|
||||
* Provides a 'Example: empty block' block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "example_empty",
|
||||
* admin_label = @Translation("Example: empty block")
|
||||
* )
|
||||
*/
|
||||
class ExampleEmptyBlock extends BlockBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* The return value of the build() method is a renderable array. Returning an
|
||||
* empty array will result in empty block contents. The front end will not
|
||||
* display empty blocks.
|
||||
*/
|
||||
public function build() {
|
||||
// We return an empty array on purpose. The block will thus not be rendered
|
||||
// on the site. See BlockExampleTest::testBlockExampleBasic().
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_example\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
|
||||
/**
|
||||
* Provides a 'Example: uppercase this please' block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "example_uppercase",
|
||||
* admin_label = @Translation("Example: uppercase this please")
|
||||
* )
|
||||
*/
|
||||
class ExampleUppercaseBlock extends BlockBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
return [
|
||||
'#markup' => t("This block's title is changed to uppercase. Any block title which contains 'uppercase' will also be changed to uppercase."),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{#
|
||||
|
||||
Description text for the Block Example.
|
||||
|
||||
#}
|
||||
|
||||
{% set block_admin_page = url('block.admin_display')['#markup'] %}
|
||||
|
||||
{% trans %}
|
||||
|
||||
<p>The Block Example provides three sample blocks which demonstrate the various
|
||||
block APIs. To experiment with the blocks, enable and configure them on
|
||||
<a href="{{ block_admin_page }}">the block admin page</a>.</p>
|
||||
|
||||
{% endtrans %}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\block_example\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Test the user-facing menus in Block Example.
|
||||
*
|
||||
* @ingroup block_example
|
||||
*
|
||||
* @group block_example
|
||||
* @group examples
|
||||
*/
|
||||
class BlockExampleMenuTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['block', 'block_example'];
|
||||
|
||||
/**
|
||||
* The installation profile to use with this test.
|
||||
*
|
||||
* This test class requires the "Tools" block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $profile = 'minimal';
|
||||
|
||||
/**
|
||||
* Test for a link to the block example in the Tools menu.
|
||||
*/
|
||||
public function testBlockExampleLink() {
|
||||
$this->drupalGet('');
|
||||
$this->assertLinkByHref('examples/block-example');
|
||||
|
||||
$this->drupalGet('examples/block-example');
|
||||
$this->assertResponse(200, 'Description page exists.');
|
||||
|
||||
// Verify that the block admin page link works.
|
||||
$this->clickLink('the block admin page');
|
||||
// Since it links to the admin page, we should get a permissions error and
|
||||
// not 404.
|
||||
$this->assertResponse(403);
|
||||
}
|
||||
|
||||
}
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\block_example\Functional;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Test the configuration options and block created by Block Example module.
|
||||
*
|
||||
* @ingroup block_example
|
||||
*
|
||||
* @group block_example
|
||||
* @group examples
|
||||
*/
|
||||
class BlockExampleTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['block', 'block_example'];
|
||||
|
||||
/**
|
||||
* Tests block_example functionality.
|
||||
*/
|
||||
public function testBlockExampleBasic() {
|
||||
$assert = $this->assertSession();
|
||||
|
||||
// Create user.
|
||||
$web_user = $this->drupalCreateUser(['administer blocks']);
|
||||
// Login the admin user.
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
$theme_name = \Drupal::config('system.theme')->get('default');
|
||||
|
||||
// Verify the blocks are listed to be added.
|
||||
$this->drupalGet('/admin/structure/block/library/' . $theme_name, ['query' => ['region' => 'content']]);
|
||||
$assert->pageTextContains('Example: configurable text');
|
||||
$assert->pageTextContains('Example: empty block');
|
||||
$assert->pageTextContains('Example: uppercase this please');
|
||||
|
||||
// Define and place blocks.
|
||||
$settings_configurable = [
|
||||
'label' => 'Configurable text',
|
||||
'id' => 'block_example_example_configurable_text',
|
||||
'theme' => $theme_name,
|
||||
];
|
||||
$this->drupalPlaceBlock('example_configurable_text', $settings_configurable);
|
||||
|
||||
$settings_uppercase = [
|
||||
'label' => 'Configurable block to be uppercased',
|
||||
'id' => 'block_example_example_uppercased',
|
||||
'theme' => $theme_name,
|
||||
];
|
||||
$this->drupalPlaceBlock('example_uppercase', $settings_uppercase);
|
||||
|
||||
$settings_empty = [
|
||||
'label' => 'Empty block',
|
||||
'id' => 'block_example_example_empty',
|
||||
'theme' => $theme_name,
|
||||
];
|
||||
$this->drupalPlaceBlock('example_empty', $settings_empty);
|
||||
|
||||
// Verify that blocks are there. Empty block will not be shown, because it
|
||||
// holds an empty array.
|
||||
$this->drupalGet('');
|
||||
$assert->pageTextContains($settings_configurable['label']);
|
||||
$assert->pageTextContains($settings_uppercase['label']);
|
||||
$assert->pageTextContains(Unicode::strtoupper($settings_uppercase['label']));
|
||||
$assert->pageTextNotContains($settings_empty['label']);
|
||||
|
||||
// Change content of configurable text block.
|
||||
$edit = [
|
||||
'settings[block_example_string_text]' => $this->randomMachineName(),
|
||||
];
|
||||
$this->drupalPostForm('/admin/structure/block/manage/' . $settings_configurable['id'], $edit, 'Save block');
|
||||
$assert->statusCodeEquals(200);
|
||||
|
||||
// Verify that new content is shown.
|
||||
$this->drupalGet('');
|
||||
$assert->statusCodeEquals(200);
|
||||
$assert->pageTextContains($edit['settings[block_example_string_text]']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
name: Cache Example
|
||||
description: Demonstrates how to use Cache API.
|
||||
package: Example modules
|
||||
# core: 8.x
|
||||
type: module
|
||||
dependencies:
|
||||
- drupal:node
|
||||
- examples:examples
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.x-dev'
|
||||
core: '8.x'
|
||||
project: 'examples'
|
||||
datestamp: 1513537386
|
||||
@@ -1,4 +0,0 @@
|
||||
cache_example.description:
|
||||
title: 'Cache Example'
|
||||
description: 'Example of Drupal Cache API'
|
||||
route_name: cache_example.description
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Outlines how a module can use the Cache API.
|
||||
*
|
||||
* @todo: Demonstrate allowing invalid cache items.
|
||||
* @todo: Demonstrate deleteing cache entries.
|
||||
* @todo: Demonstrate deleteing cache tags.
|
||||
* @todo: Demonstrate deleteing all cache items.
|
||||
* @todo: Demonstrate invalidating cache entries.
|
||||
* @todo: Demonstrate invalidating cache tags.
|
||||
* @todo: Demonstrate invalidating all cache items.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup cache_example Example: Cache API
|
||||
* @ingroup examples
|
||||
* @{
|
||||
* Outlines how a module can use the Cache API.
|
||||
*
|
||||
* Cache API allows us to cache data that is heavy to calculate. As this can
|
||||
* significantly speed up the Drupal site, it is recommended to use cache
|
||||
* mechanism when it is appropriate.
|
||||
*
|
||||
* Cache in Drupal is very easy to use. This example will search entire Drupal
|
||||
* folder and display all files. Since this operation includes filesystem it can
|
||||
* take a while. This list will not change much on production
|
||||
* websites, so we decide to cache it.
|
||||
*
|
||||
* @see \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
|
||||
/**
|
||||
* @} End of "defgroup cache_example".
|
||||
*/
|
||||
@@ -1,6 +0,0 @@
|
||||
cache_example.description:
|
||||
path: 'examples/cache-example'
|
||||
defaults:
|
||||
_form: '\Drupal\cache_example\Form\CacheExampleForm'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
@@ -1,306 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\cache_example\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Session\AccountProxyInterface;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
|
||||
/**
|
||||
* Form with examples on how to use cache.
|
||||
*/
|
||||
class CacheExampleForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountProxyInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The cache.default cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cacheBackend;
|
||||
|
||||
/**
|
||||
* Dependency injection through the constructor.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack service.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation
|
||||
* The string translation service.
|
||||
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
|
||||
* The current active user service.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* The cache object associated with the default bin.
|
||||
*/
|
||||
public function __construct(
|
||||
RequestStack $request_stack,
|
||||
TranslationInterface $translation,
|
||||
AccountProxyInterface $current_user,
|
||||
CacheBackendInterface $cache_backend
|
||||
) {
|
||||
$this->setRequestStack($request_stack);
|
||||
$this->setStringTranslation($translation);
|
||||
$this->currentUser = $current_user;
|
||||
$this->cacheBackend = $cache_backend;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
// Forms that require a Drupal service or a custom service should access
|
||||
// the service using dependency injection.
|
||||
// @link https://www.drupal.org/node/2203931.
|
||||
// Those services are passed in the $container through the static create
|
||||
// method.
|
||||
return new static(
|
||||
$container->get('request_stack'),
|
||||
$container->get('string_translation'),
|
||||
$container->get('current_user'),
|
||||
$container->get('cache.default')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'cron_cache';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
// Log execution time.
|
||||
$start_time = microtime(TRUE);
|
||||
|
||||
// Try to load the files count from cache. This function will accept two
|
||||
// arguments:
|
||||
// - cache object name (cid)
|
||||
// - cache bin, the (optional) cache bin (most often a database table) where
|
||||
// the object is to be saved.
|
||||
//
|
||||
// cache_get() returns the cached object or FALSE if object does not exist.
|
||||
if ($cache = $this->cacheBackend->get('cache_example_files_count')) {
|
||||
/*
|
||||
* Get cached data. Complex data types will be unserialized automatically.
|
||||
*/
|
||||
$files_count = $cache->data;
|
||||
}
|
||||
else {
|
||||
// If there was no cached data available we have to search filesystem.
|
||||
// Recursively get all .PHP files from Drupal's core folder.
|
||||
$files_count = count(file_scan_directory('core', '/.php/'));
|
||||
|
||||
// Since we have recalculated, we now need to store the new data into
|
||||
// cache. Complex data types will be automatically serialized before
|
||||
// being saved into cache.
|
||||
// Here we use the default setting and create an unexpiring cache item.
|
||||
// See below for an example that creates an expiring cache item.
|
||||
$this->cacheBackend->set('cache_example_files_count', $files_count, CacheBackendInterface::CACHE_PERMANENT);
|
||||
}
|
||||
|
||||
$end_time = microtime(TRUE);
|
||||
$duration = $end_time - $start_time;
|
||||
|
||||
// Format intro message.
|
||||
$intro_message = '<p>' . $this->t("This example will search Drupal's core folder and display a count of the PHP files in it.") . ' ';
|
||||
$intro_message .= $this->t('This can take a while, since there are a lot of files to be searched.') . ' ';
|
||||
$intro_message .= $this->t('We will search filesystem just once and save output to the cache. We will use cached data for later requests.') . '</p>';
|
||||
$intro_message .= '<p>'
|
||||
. $this->t(
|
||||
'<a href="@url">Reload this page</a> to see cache in action.',
|
||||
['@url' => $this->getRequest()->getRequestUri()]
|
||||
)
|
||||
. ' ';
|
||||
$intro_message .= $this->t('You can use the button below to remove cached data.') . '</p>';
|
||||
|
||||
$form['file_search'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('File search caching'),
|
||||
];
|
||||
$form['file_search']['introduction'] = [
|
||||
'#markup' => $intro_message,
|
||||
];
|
||||
|
||||
$color = empty($cache) ? 'red' : 'green';
|
||||
$retrieval = empty($cache) ? $this->t('calculated by traversing the filesystem') : $this->t('retrieved from cache');
|
||||
|
||||
$form['file_search']['statistics'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => $this->t('%count files exist in this Drupal installation; @retrieval in @time ms. <br/>(Source: <span style="color:@color;">@source</span>)', [
|
||||
'%count' => $files_count,
|
||||
'@retrieval' => $retrieval,
|
||||
'@time' => number_format($duration * 1000, 2),
|
||||
'@color' => $color,
|
||||
'@source' => empty($cache) ? $this->t('actual file search') : $this->t('cached'),
|
||||
]
|
||||
),
|
||||
];
|
||||
$form['file_search']['remove_file_count'] = [
|
||||
'#type' => 'submit',
|
||||
'#submit' => [[$this, 'expireFiles']],
|
||||
'#value' => $this->t('Explicitly remove cached file count'),
|
||||
];
|
||||
|
||||
$form['expiration_demo'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Cache expiration settings'),
|
||||
];
|
||||
$form['expiration_demo']['explanation'] = [
|
||||
'#markup' => $this->t('A cache item can be set as CACHE_PERMANENT, meaning that it will only be removed when explicitly cleared, or it can have an expiration time (a Unix timestamp).'),
|
||||
];
|
||||
|
||||
$item = $this->cacheBackend->get('cache_example_expiring_item', TRUE);
|
||||
if ($item == FALSE) {
|
||||
$item_status = $this->t('Cache item does not exist');
|
||||
}
|
||||
else {
|
||||
$item_status = $item->valid ? $this->t('Cache item exists and is set to expire at %time', ['%time' => $item->data]) :
|
||||
$this->t('Cache_item is invalid');
|
||||
}
|
||||
|
||||
$form['expiration_demo']['current_status'] = [
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Current status of cache item "cache_example_expiring_item"'),
|
||||
'#markup' => $item_status,
|
||||
];
|
||||
$form['expiration_demo']['expiration'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Time before cache expiration'),
|
||||
'#options' => [
|
||||
'never_remove' => $this->t('CACHE_PERMANENT'),
|
||||
-10 => $this->t('Immediate expiration'),
|
||||
10 => $this->t('10 seconds from form submission'),
|
||||
60 => $this->t('1 minute from form submission'),
|
||||
300 => $this->t('5 minutes from form submission'),
|
||||
],
|
||||
'#default_value' => -10,
|
||||
'#description' => $this->t('Any cache item can be set to only expire when explicitly cleared, or to expire at a given time.'),
|
||||
];
|
||||
$form['expiration_demo']['create_cache_item'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Create a cache item with this expiration'),
|
||||
'#submit' => [[$this, 'createExpiringItem']],
|
||||
];
|
||||
|
||||
$form['cache_clearing'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Expire and remove options'),
|
||||
'#description' => $this->t("We have APIs to expire cached items and also to just remove them. Unfortunately, they're all the same API, cache_clear_all"),
|
||||
];
|
||||
$form['cache_clearing']['cache_clear_type'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Type of cache clearing to do'),
|
||||
'#options' => [
|
||||
'expire' => $this->t('Remove items from the "cache" bin that have expired'),
|
||||
'remove_all' => $this->t('Remove all items from the "cache" bin regardless of expiration'),
|
||||
'remove_tag' => $this->t('Remove all items in the "cache" bin with the tag "cache_example" set to 1'),
|
||||
],
|
||||
'#default_value' => 'expire',
|
||||
];
|
||||
// Submit button to clear cached data.
|
||||
$form['cache_clearing']['clear_expired'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Clear or expire cache'),
|
||||
'#submit' => [[$this, 'cacheClearing']],
|
||||
'#access' => $this->currentUser->hasPermission('administer site configuration'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler that explicitly clears cache_example_files_count from cache.
|
||||
*/
|
||||
public function expireFiles($form, &$form_state) {
|
||||
// Clear cached data. This function will delete cached object from cache
|
||||
// bin.
|
||||
//
|
||||
// The first argument is cache id to be deleted. Since we've provided it
|
||||
// explicitly, it will be removed whether or not it has an associated
|
||||
// expiration time. The second argument (required here) is the cache bin.
|
||||
// Using cache_clear_all() explicitly in this way
|
||||
// forces removal of the cached item.
|
||||
$this->cacheBackend->delete('cache_example_files_count');
|
||||
|
||||
// Display message to the user.
|
||||
drupal_set_message($this->t('Cached data key "cache_example_files_count" was cleared.'), 'status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler to create a new cache item with specified expiration.
|
||||
*/
|
||||
public function createExpiringItem($form, &$form_state) {
|
||||
|
||||
$tags = [
|
||||
'cache_example:1',
|
||||
];
|
||||
|
||||
$interval = $form_state->getValue('expiration');
|
||||
if ($interval == 'never_remove') {
|
||||
$expiration = CacheBackendInterface::CACHE_PERMANENT;
|
||||
$expiration_friendly = $this->t('Never expires');
|
||||
}
|
||||
else {
|
||||
$expiration = time() + $interval;
|
||||
$expiration_friendly = format_date($expiration);
|
||||
}
|
||||
// Set the expiration to the actual Unix timestamp of the end of the
|
||||
// required interval. Also add a tag to it to be able to clear caches more
|
||||
// precise.
|
||||
$this->cacheBackend->set('cache_example_expiring_item', $expiration_friendly, $expiration, $tags);
|
||||
drupal_set_message($this->t('cache_example_expiring_item was set to expire at %time', ['%time' => $expiration_friendly]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler to demonstrate the various uses of cache_clear_all().
|
||||
*/
|
||||
public function cacheClearing($form, &$form_state) {
|
||||
switch ($form_state->getValue('cache_clear_type')) {
|
||||
case 'expire':
|
||||
// Here we'll remove all cache keys in the 'cache' bin that have
|
||||
// expired.
|
||||
$this->cacheBackend->garbageCollection();
|
||||
drupal_set_message($this->t('\Drupal::cache()->garbageCollection() was called, removing any expired cache items.'));
|
||||
break;
|
||||
|
||||
case 'remove_all':
|
||||
// This removes all keys in a bin using a super-wildcard. This
|
||||
// has nothing to do with expiration. It's just brute-force removal.
|
||||
$this->cacheBackend->deleteAll();
|
||||
drupal_set_message($this->t('ALL entries in the "cache" bin were removed with \Drupal::cache()->deleteAll().'));
|
||||
break;
|
||||
|
||||
case 'remove_tag':
|
||||
// This removes cache entries with the tag "cache_example" set to 1 in
|
||||
// the "cache".
|
||||
$tags = [
|
||||
'cache_example:1',
|
||||
];
|
||||
Cache::invalidateTags($tags);
|
||||
drupal_set_message($this->t('Cache entries with the tag "cache_example" set to 1 in the "cache" bin were invalidated with \Drupal\Core\Cache\Cache::invalidateTags($tags).'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-110
@@ -1,110 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\cache_example\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests for the cache_example module.
|
||||
*
|
||||
* @ingroup cache_example
|
||||
*
|
||||
* @group cache_example
|
||||
* @group examples
|
||||
*/
|
||||
class CacheExampleTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['cache_example'];
|
||||
|
||||
/**
|
||||
* The installation profile to use with this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $profile = 'minimal';
|
||||
|
||||
/**
|
||||
* Test menu links and routes.
|
||||
*
|
||||
* Test the following:
|
||||
* - A link to the cache_example in the Tools menu.
|
||||
* - That you can successfully access the cache_example form.
|
||||
*/
|
||||
public function testCacheExampleMenu() {
|
||||
|
||||
$assert = $this->assertSession();
|
||||
|
||||
// Test for a link to the cache_example in the Tools menu.
|
||||
$this->drupalGet('');
|
||||
$assert->statusCodeEquals(200);
|
||||
|
||||
$assert->linkByHrefExists('examples/cache-example');
|
||||
|
||||
// Verify if the can successfully access the cache_example form.
|
||||
$this->drupalGet('examples/cache-example');
|
||||
$assert->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that our caches function.
|
||||
*
|
||||
* Does the following:
|
||||
* - Load cache example page and test if displaying uncached version.
|
||||
* - Reload once again and test if displaying cached version.
|
||||
* - Find reload link and click on it.
|
||||
* - Clear cache at the end and test if displaying uncached version again.
|
||||
*/
|
||||
public function testCacheExampleBasic() {
|
||||
$assert = $this->assertSession();
|
||||
|
||||
// We need administrative privileges to clear the cache.
|
||||
$admin_user = $this->drupalCreateUser(['administer site configuration']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Get initial page cache example page, first time accessed,
|
||||
// and assert uncached output.
|
||||
$this->drupalGet('examples/cache-example');
|
||||
$assert->pageTextContains('Source: actual file search');
|
||||
|
||||
// Reload the page; the number should be cached.
|
||||
$this->drupalGet('examples/cache-example');
|
||||
$assert->pageTextContains('Source: cached');
|
||||
|
||||
// Now push the button to remove the count.
|
||||
$this->drupalPostForm('examples/cache-example', [], 'Explicitly remove cached file count');
|
||||
$assert->pageTextContains('Source: actual file search');
|
||||
|
||||
// Create a cached item. First make sure it doesn't already exist.
|
||||
$assert->pageTextContains('Cache item does not exist');
|
||||
$this->drupalPostForm('examples/cache-example', ['expiration' => -10], 'Create a cache item with this expiration');
|
||||
// We should now have an already-expired item. Automatically invalid.
|
||||
$assert->pageTextContains('Cache_item is invalid');
|
||||
// Now do the expiration operation.
|
||||
$this->drupalPostForm('examples/cache-example', ['cache_clear_type' => 'expire'], 'Clear or expire cache');
|
||||
// And verify that it was removed.
|
||||
$assert->pageTextContains('Cache item does not exist');
|
||||
|
||||
// Create a cached item. This time we'll make it not expire.
|
||||
$this->drupalPostForm('examples/cache-example', ['expiration' => 'never_remove'], 'Create a cache item with this expiration');
|
||||
// We should now have an never-remove item.
|
||||
$assert->pageTextContains('Cache item exists and is set to expire at Never expires');
|
||||
// Now do the expiration operation.
|
||||
$this->drupalPostForm('examples/cache-example', ['cache_clear_type' => 'expire'], 'Clear or expire cache');
|
||||
// And verify that it was not removed.
|
||||
$assert->pageTextContains('Cache item exists and is set to expire at Never expires');
|
||||
// Now do tag invalidation.
|
||||
$this->drupalPostForm('examples/cache-example', ['cache_clear_type' => 'remove_tag'], 'Clear or expire cache');
|
||||
// And verify that it was invalidated.
|
||||
$assert->pageTextContains('Cache_item is invalid');
|
||||
// Do the hard delete.
|
||||
$this->drupalPostForm('examples/cache-example', ['cache_clear_type' => 'remove_all'], 'Clear or expire cache');
|
||||
// And verify that it was removed.
|
||||
$assert->pageTextContains('Cache item does not exist');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "drupal/examples",
|
||||
"description": "The Examples for Developers project aims to provide high-quality, well-documented API examples for a broad range of Drupal core functionality.",
|
||||
"type": "drupal-module",
|
||||
"homepage": "https://www.drupal.org/project/examples",
|
||||
"support": {
|
||||
"issues": "https://www.drupal.org/project/issues/examples",
|
||||
"documentation": "https://api.drupal.org/api/examples",
|
||||
"source": "http://cgit.drupalcode.org/examples"
|
||||
},
|
||||
"license": "GPL-2.0+"
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
# This file defines a default config entity. This allows the module to include
|
||||
# config entities that are present 'out of the box'. Default config entities
|
||||
# are created in Drupal when the module is enabled. They are removed when the
|
||||
# module is uninstalled.
|
||||
|
||||
# Default config entities can be edited by the user within Drupal. This edited
|
||||
# entity wil become exportable through the configuration system. This file,
|
||||
# however, will remain untouched.
|
||||
|
||||
# For our config entity to be added at installation time, we have to place it in
|
||||
# the config/install directory of our module. Thus our file is located at:
|
||||
# config/install/config_entity_example.robot.marvin.yml.
|
||||
|
||||
# You can see where these properties are defined in the annotation of
|
||||
# Drupal\config_entity_example\Entity\Robot.
|
||||
|
||||
# The id of the config entity.
|
||||
id: marvin
|
||||
# Our properties follow.
|
||||
label: 'Marvin, the paranoid android'
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
# Schema for the configuration files of the Config Entity Example module.
|
||||
|
||||
# This schema tells the config system how to read our config YML files.
|
||||
# See for example the file config/config_entity_example.robot.marvin.yml, which
|
||||
# contains our default config entity.
|
||||
|
||||
# Documentation for schema files like this one is located here:
|
||||
# https://drupal.org/node/1905070
|
||||
|
||||
config_entity_example.robot.*:
|
||||
type: mapping
|
||||
label: 'Robot'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'Robot id'
|
||||
uuid:
|
||||
type: string
|
||||
label: 'UUID'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
floopy:
|
||||
type: boolean
|
||||
label: 'Floopy'
|
||||
langcode:
|
||||
type: string
|
||||
label: 'Default language'
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
name: 'Config Entity Example'
|
||||
type: module
|
||||
description: 'Demonstrates how to create a config entity type.'
|
||||
package: Example modules
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- examples:examples
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.x-dev'
|
||||
core: '8.x'
|
||||
project: 'examples'
|
||||
datestamp: 1513537386
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
# Add some local task links to facilitate navigation.
|
||||
|
||||
config_entity_example.add_action:
|
||||
route_name: entity.robot.add_form
|
||||
title: 'Add robot'
|
||||
appears_on:
|
||||
- entity.robot.list
|
||||
|
||||
config_entity_example.list_action:
|
||||
route_name: entity.robot.list
|
||||
title: 'List robots'
|
||||
appears_on:
|
||||
- entity.robot.add_form
|
||||
- entity.robot.edit_form
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
config_entity_example.menu:
|
||||
title: Config Entity Example
|
||||
route_name: entity.robot.list
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Demonstrates how to define a new configuration entity type.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup config_entity_example Example: Config Entity
|
||||
* @ingroup examples
|
||||
* @{
|
||||
* Implement a Config Entity.
|
||||
*
|
||||
* This module demonstrates implementing a Config Entity.
|
||||
*
|
||||
* This is an example of a simple configuration entity, the kind you might
|
||||
* create to store administrator-defined objects like blocks or views.
|
||||
*
|
||||
* In this module we define a configuration entity named 'Robot'. The entity
|
||||
* has a unique ID (also called a machine name), a human-readable label used
|
||||
* for display, and a universally unique identifier. You can create new robots
|
||||
* by navigating to <your_site_root>/examples/config_entity_example. A
|
||||
* default robot, "marvin", is included with the module.
|
||||
*
|
||||
* What's special about a Configuration Entity?
|
||||
*
|
||||
* Configuration entities are entities just like content entities. The key
|
||||
* difference is where the data is stored. Content entities are stored in the
|
||||
* database. Configuration entities are stored in *.yml files, typically under
|
||||
* <your_site_root>/sites/default/files/config_<unique_id>.
|
||||
*
|
||||
* Another key difference with configuration entities is the expectation they
|
||||
* are created by administrators, and not end users. As files, configuration
|
||||
* entities can be added to a version control system.
|
||||
*
|
||||
* Originally based on code from blog post at
|
||||
* http://previousnext.com.au/blog/understanding-drupal-8s-config-entities
|
||||
*/
|
||||
|
||||
/**
|
||||
* @} End of "defgroup config_entity_example".
|
||||
*/
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
#Define the administer permission for the robot config entity.
|
||||
'administer robots':
|
||||
title: 'Administer robots'
|
||||
description: Create and edit robots.
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
# The routing.yml file defines the paths for our module.
|
||||
# Here we define the paths for our entity type's admin UI.
|
||||
|
||||
# This is the router item for listing all entities.
|
||||
entity.robot.list:
|
||||
path: '/examples/config-entity-example'
|
||||
defaults:
|
||||
# '_entity_list' tells Drupal to use an entity list controller.
|
||||
# We give the entity ID here. Drupal then looks in the entity's annotation
|
||||
# and looks for the "list" entry under "controllers" for the class to load.
|
||||
# @see \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer
|
||||
_entity_list: 'robot'
|
||||
_title: 'Config Entity Example'
|
||||
requirements:
|
||||
_permission: 'administer robots'
|
||||
|
||||
# This is the router item for adding our entity.
|
||||
entity.robot.add_form:
|
||||
path: '/examples/config-entity-example/add'
|
||||
defaults:
|
||||
_title: 'Add robot'
|
||||
# Like _entity_list above, _entity_form gives the entity type ID, only this
|
||||
# time also lists the form separated by a period. Drupal looks in the
|
||||
# annotation for the entity and locates the "add" entry under "form" for
|
||||
# the form class to load.
|
||||
# @see \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer
|
||||
_entity_form: robot.add
|
||||
requirements:
|
||||
_entity_create_access: robot
|
||||
|
||||
# This is the router item for editing our entity.
|
||||
entity.robot.edit_form:
|
||||
# Parameters may be passed to the form via the URL path. We name the
|
||||
# parameter in the path by enclosing it in curly braces. For entity forms,
|
||||
# we include the entity ID in the path by including a parameter with the
|
||||
# same name as the entity type ID.
|
||||
path: '/examples/config-entity-example/manage/{robot}'
|
||||
defaults:
|
||||
_title: 'Edit robot'
|
||||
# List our add entry above, this _entity_form entry instructs Drupal to
|
||||
# read our entity type's annonation, and look for the "edit" entry under
|
||||
# "form".
|
||||
_entity_form: robot.edit
|
||||
requirements:
|
||||
# This uses our entity access controller.
|
||||
# @see \Drupal\Core\Entity\EntityAccessCheck
|
||||
_entity_access: robot.update
|
||||
|
||||
# This is the router item for deleting an instance of our entity.
|
||||
entity.robot.delete_form:
|
||||
path: '/examples/config-entity-example/manage/{robot}/delete'
|
||||
defaults:
|
||||
_title: 'Delete robot'
|
||||
_entity_form: robot.delete
|
||||
requirements:
|
||||
_entity_access: robot.delete
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\config_entity_example\Controller;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\examples\Utility\DescriptionTemplateTrait;
|
||||
|
||||
/**
|
||||
* Provides a listing of robot entities.
|
||||
*
|
||||
* List Controllers provide a list of entities in a tabular form. The base
|
||||
* class provides most of the rendering logic for us. The key functions
|
||||
* we need to override are buildHeader() and buildRow(). These control what
|
||||
* columns are displayed in the table, and how each row is displayed
|
||||
* respectively.
|
||||
*
|
||||
* Drupal locates the list controller by looking for the "list" entry under
|
||||
* "controllers" in our entity type's annotation. We define the path on which
|
||||
* the list may be accessed in our module's *.routing.yml file. The key entry
|
||||
* to look for is "_entity_list". In *.routing.yml, "_entity_list" specifies
|
||||
* an entity type ID. When a user navigates to the URL for that router item,
|
||||
* Drupal loads the annotation for that entity type. It looks for the "list"
|
||||
* entry under "controllers" for the class to load.
|
||||
*
|
||||
* @ingroup config_entity_example
|
||||
*/
|
||||
class RobotListBuilder extends ConfigEntityListBuilder {
|
||||
use DescriptionTemplateTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getModuleName() {
|
||||
return 'config_entity_example';
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the header row for the entity listing.
|
||||
*
|
||||
* @return array
|
||||
* A render array structure of header strings.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityListController::render()
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['label'] = $this->t('Robot');
|
||||
$header['machine_name'] = $this->t('Machine Name');
|
||||
$header['floopy'] = $this->t('Floopy');
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a row for an entity in the entity listing.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity for which to build the row.
|
||||
*
|
||||
* @return array
|
||||
* A render array of the table row for displaying the entity.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityListController::render()
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row['label'] = $entity->label();
|
||||
$row['machine_name'] = $entity->id();
|
||||
$row['floopy'] = $entity->floopy;
|
||||
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds some descriptive text to our entity list.
|
||||
*
|
||||
* Typically, there's no need to override render(). You may wish to do so,
|
||||
* however, if you want to add markup before or after the table.
|
||||
*
|
||||
* @return array
|
||||
* Renderable array.
|
||||
*/
|
||||
public function render() {
|
||||
$build = $this->description();
|
||||
$build[] = parent::render();
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\config_entity_example\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
|
||||
/**
|
||||
* Defines the robot entity.
|
||||
*
|
||||
* The lines below, starting with '@ConfigEntityType,' are a plugin annotation.
|
||||
* These define the entity type to the entity type manager.
|
||||
*
|
||||
* The properties in the annotation are as follows:
|
||||
* - id: The machine name of the entity type.
|
||||
* - label: The human-readable label of the entity type. We pass this through
|
||||
* the "@Translation" wrapper so that the multilingual system may
|
||||
* translate it in the user interface.
|
||||
* - handlers: An array of entity handler classes, keyed by handler type.
|
||||
* - access: The class that is used for access checks.
|
||||
* - list_builder: The class that provides listings of the entity.
|
||||
* - form: An array of entity form classes keyed by their operation.
|
||||
* - entity_keys: Specifies the class properties in which unique keys are
|
||||
* stored for this entity type. Unique keys are properties which you know
|
||||
* will be unique, and which the entity manager can use as unique in database
|
||||
* queries.
|
||||
* - links: entity URL definitions. These are mostly used for Field UI.
|
||||
* Arbitrary keys can set here. For example, User sets cancel-form, while
|
||||
* Node uses delete-form.
|
||||
*
|
||||
* @see http://previousnext.com.au/blog/understanding-drupal-8s-config-entities
|
||||
* @see annotation
|
||||
* @see Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup config_entity_example
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "robot",
|
||||
* label = @Translation("Robot"),
|
||||
* admin_permission = "administer robots",
|
||||
* handlers = {
|
||||
* "access" = "Drupal\config_entity_example\RobotAccessController",
|
||||
* "list_builder" = "Drupal\config_entity_example\Controller\RobotListBuilder",
|
||||
* "form" = {
|
||||
* "add" = "Drupal\config_entity_example\Form\RobotAddForm",
|
||||
* "edit" = "Drupal\config_entity_example\Form\RobotEditForm",
|
||||
* "delete" = "Drupal\config_entity_example\Form\RobotDeleteForm"
|
||||
* }
|
||||
* },
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "label"
|
||||
* },
|
||||
* links = {
|
||||
* "edit-form" = "/examples/config_entity_example/manage/{robot}",
|
||||
* "delete-form" = "/examples/config_entity_example/manage/{robot}/delete"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class Robot extends ConfigEntityBase {
|
||||
|
||||
/**
|
||||
* The robot ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The robot UUID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $uuid;
|
||||
|
||||
/**
|
||||
* The robot label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* The robot floopy flag.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $floopy;
|
||||
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\config_entity_example\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Class RobotAddForm.
|
||||
*
|
||||
* Provides the add form for our Robot entity.
|
||||
*
|
||||
* @ingroup config_entity_example
|
||||
*/
|
||||
class RobotAddForm extends RobotFormBase {
|
||||
|
||||
/**
|
||||
* Returns the actions provided by this form.
|
||||
*
|
||||
* For our add form, we only need to change the text of the submit button.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* An array of supported actions for the current entity form.
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions = parent::actions($form, $form_state);
|
||||
$actions['submit']['#value'] = $this->t('Create Robot');
|
||||
return $actions;
|
||||
}
|
||||
|
||||
}
|
||||
-89
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\config_entity_example\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityConfirmFormBase;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Class RobotDeleteForm.
|
||||
*
|
||||
* Provides a confirm form for deleting the entity. This is different from the
|
||||
* add and edit forms as it does not inherit from RobotFormBase. The reason for
|
||||
* this is that we do not need to build the same form. Instead, we present the
|
||||
* user with a simple yes/no question. For this reason, we derive from
|
||||
* EntityConfirmFormBase instead.
|
||||
*
|
||||
* @ingroup config_entity_example
|
||||
*/
|
||||
class RobotDeleteForm extends EntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* Gathers a confirmation question.
|
||||
*
|
||||
* The question is used as a title in our confirm form. For delete confirm
|
||||
* forms, this typically takes the form of "Are you sure you want to
|
||||
* delete...", including the entity label.
|
||||
*
|
||||
* @return string
|
||||
* Translated string.
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete robot %label?', [
|
||||
'%label' => $this->entity->label(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather the confirmation text.
|
||||
*
|
||||
* The confirm text is used as the text in the button that confirms the
|
||||
* question posed by getQuestion().
|
||||
*
|
||||
* @return string
|
||||
* Translated string.
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete Robot');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cancel URL.
|
||||
*
|
||||
* Provides the URL to go to if the user cancels the action. For entity
|
||||
* delete forms, this is typically the route that points at the list
|
||||
* controller.
|
||||
*
|
||||
* @return \Drupal\Core\Url
|
||||
* The URL to go to if the user cancels the deletion.
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.robot.list');
|
||||
}
|
||||
|
||||
/**
|
||||
* The submit handler for the confirm form.
|
||||
*
|
||||
* For entity delete forms, you use this to delete the entity in
|
||||
* $this->entity.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Delete the entity.
|
||||
$this->entity->delete();
|
||||
|
||||
// Set a message that the entity was deleted.
|
||||
drupal_set_message($this->t('Robot %label was deleted.', [
|
||||
'%label' => $this->entity->label(),
|
||||
]));
|
||||
|
||||
// Redirect the user to the list controller when complete.
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\config_entity_example\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Class RobotEditForm.
|
||||
*
|
||||
* Provides the edit form for our Robot entity.
|
||||
*
|
||||
* @ingroup config_entity_example
|
||||
*/
|
||||
class RobotEditForm extends RobotFormBase {
|
||||
|
||||
/**
|
||||
* Returns the actions provided by this form.
|
||||
*
|
||||
* For the edit form, we only need to change the text of the submit button.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* An array of supported actions for the current entity form.
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions = parent::actions($form, $form_state);
|
||||
$actions['submit']['#value'] = $this->t('Update Robot');
|
||||
return $actions;
|
||||
}
|
||||
|
||||
}
|
||||
-221
@@ -1,221 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\config_entity_example\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\Query\QueryFactory;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Link;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class RobotFormBase.
|
||||
*
|
||||
* Typically, we need to build the same form for both adding a new entity,
|
||||
* and editing an existing entity. Instead of duplicating our form code,
|
||||
* we create a base class. Drupal never routes to this class directly,
|
||||
* but instead through the child classes of RobotAddForm and RobotEditForm.
|
||||
*
|
||||
* @ingroup config_entity_example
|
||||
*/
|
||||
class RobotFormBase extends EntityForm {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\Query\QueryFactory
|
||||
*/
|
||||
protected $entityQueryFactory;
|
||||
|
||||
/**
|
||||
* Construct the RobotFormBase.
|
||||
*
|
||||
* For simple entity forms, there's no need for a constructor. Our robot form
|
||||
* base, however, requires an entity query factory to be injected into it
|
||||
* from the container. We later use this query factory to build an entity
|
||||
* query for the exists() method.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
|
||||
* An entity query factory for the robot entity type.
|
||||
*/
|
||||
public function __construct(QueryFactory $query_factory) {
|
||||
$this->entityQueryFactory = $query_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for RobotFormBase.
|
||||
*
|
||||
* When Drupal builds this class it does not call the constructor directly.
|
||||
* Instead, it relies on this method to build the new object. Why? The class
|
||||
* constructor may take multiple arguments that are unknown to Drupal. The
|
||||
* create() method always takes one parameter -- the container. The purpose
|
||||
* of the create() method is twofold: It provides a standard way for Drupal
|
||||
* to construct the object, meanwhile it provides you a place to get needed
|
||||
* constructor parameters from the container.
|
||||
*
|
||||
* In this case, we ask the container for an entity query factory. We then
|
||||
* pass the factory to our class as a constructor parameter.
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static($container->get('entity.query'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\EntityFormController::form().
|
||||
*
|
||||
* Builds the entity add/edit form.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* An associative array containing the robot add/edit form.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
// Get anything we need from the base class.
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
// Drupal provides the entity to us as a class variable. If this is an
|
||||
// existing entity, it will be populated with existing values as class
|
||||
// variables. If this is a new entity, it will be a new object with the
|
||||
// class of our entity. Drupal knows which class to call from the
|
||||
// annotation on our Robot class.
|
||||
$robot = $this->entity;
|
||||
|
||||
// Build the form.
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Label'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $robot->label(),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
$form['id'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#title' => $this->t('Machine name'),
|
||||
'#default_value' => $robot->id(),
|
||||
'#machine_name' => [
|
||||
'exists' => [$this, 'exists'],
|
||||
'replace_pattern' => '([^a-z0-9_]+)|(^custom$)',
|
||||
'error' => 'The machine-readable name must be unique, and can only contain lowercase letters, numbers, and underscores. Additionally, it can not be the reserved word "custom".',
|
||||
],
|
||||
'#disabled' => !$robot->isNew(),
|
||||
];
|
||||
$form['floopy'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Floopy'),
|
||||
'#default_value' => $robot->floopy,
|
||||
];
|
||||
|
||||
// Return the form.
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for an existing robot.
|
||||
*
|
||||
* @param string|int $entity_id
|
||||
* The entity ID.
|
||||
* @param array $element
|
||||
* The form element.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if this format already exists, FALSE otherwise.
|
||||
*/
|
||||
public function exists($entity_id, array $element, FormStateInterface $form_state) {
|
||||
// Use the query factory to build a new robot entity query.
|
||||
$query = $this->entityQueryFactory->get('robot');
|
||||
|
||||
// Query the entity ID to see if its in use.
|
||||
$result = $query->condition('id', $element['#field_prefix'] . $entity_id)
|
||||
->execute();
|
||||
|
||||
// We don't need to return the ID, only if it exists or not.
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\EntityFormController::actions().
|
||||
*
|
||||
* To set the submit button text, we need to override actions().
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* An array of supported actions for the current entity form.
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
// Get the basic actins from the base class.
|
||||
$actions = parent::actions($form, $form_state);
|
||||
|
||||
// Change the submit button text.
|
||||
$actions['submit']['#value'] = $this->t('Save');
|
||||
|
||||
// Return the result.
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\EntityFormController::validate().
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*/
|
||||
public function validate(array $form, FormStateInterface $form_state) {
|
||||
parent::validate($form, $form_state);
|
||||
|
||||
// Add code here to validate your config entity's form elements.
|
||||
// Nothing to do here.
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\EntityFormController::save().
|
||||
*
|
||||
* Saves the entity. This is called after submit() has built the entity from
|
||||
* the form values. Do not override submit() as save() is the preferred
|
||||
* method for entity form controllers.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
// EntityForm provides us with the entity we're working on.
|
||||
$robot = $this->getEntity();
|
||||
|
||||
// Drupal already populated the form values in the entity object. Each
|
||||
// form field was saved as a public variable in the entity class. PHP
|
||||
// allows Drupal to do this even if the method is not defined ahead of
|
||||
// time.
|
||||
$status = $robot->save();
|
||||
|
||||
// Grab the URL of the new entity. We'll use it in the message.
|
||||
$url = $robot->urlInfo();
|
||||
|
||||
// Create an edit link.
|
||||
$edit_link = Link::fromTextAndUrl($this->t('Edit'), $url)->toString();
|
||||
|
||||
if ($status == SAVED_UPDATED) {
|
||||
// If we edited an existing entity...
|
||||
drupal_set_message($this->t('Robot %label has been updated.', ['%label' => $robot->label()]));
|
||||
$this->logger('contact')->notice('Robot %label has been updated.', ['%label' => $robot->label(), 'link' => $edit_link]);
|
||||
}
|
||||
else {
|
||||
// If we created a new entity...
|
||||
drupal_set_message($this->t('Robot %label has been added.', ['%label' => $robot->label()]));
|
||||
$this->logger('contact')->notice('Robot %label has been added.', ['%label' => $robot->label(), 'link' => $edit_link]);
|
||||
}
|
||||
|
||||
// Redirect the user back to the listing route after the save operation.
|
||||
$form_state->setRedirect('entity.robot.list');
|
||||
}
|
||||
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\config_entity_example;
|
||||
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines an access controller for the robot entity.
|
||||
*
|
||||
* We set this class to be the access controller in Robot's entity annotation.
|
||||
*
|
||||
* @see \Drupal\config_entity_example\Entity\Robot
|
||||
*
|
||||
* @ingroup config_entity_example
|
||||
*/
|
||||
class RobotAccessController extends EntityAccessControlHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
// The $opereration parameter tells you what sort of operation access is
|
||||
// being checked for.
|
||||
if ($operation == 'view') {
|
||||
return TRUE;
|
||||
}
|
||||
// Other than the view operation, we're going to be insanely lax about
|
||||
// access. Don't try this at home!
|
||||
return parent::checkAccess($entity, $operation, $account);
|
||||
}
|
||||
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
{#
|
||||
|
||||
Description text for the Config Entity Example.
|
||||
|
||||
#}
|
||||
|
||||
{% trans %}
|
||||
|
||||
<p>The Config Entity Example module defines a Robot entity type. This is a list
|
||||
of the Robot entities currently in your Drupal site.</p><p>By default, when
|
||||
you enable this module, one entity is created from configuration. This is
|
||||
why we call them Config Entities. Marvin, the paranoid android, is created
|
||||
in the database when the module is enabled.</p><p>You can view a list of
|
||||
Robots here. You can also use the 'Operations' column to edit and delete
|
||||
Robots.</p>
|
||||
|
||||
{% endtrans %}
|
||||
-172
@@ -1,172 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\config_entity_example\Functional;
|
||||
use Drupal\config_entity_example\Entity\Robot;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Test the Config Entity Example module.
|
||||
*
|
||||
* @group config_entity_example
|
||||
* @group examples
|
||||
*
|
||||
* @ingroup config_entity_example
|
||||
*/
|
||||
class ConfigEntityExampleTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['config_entity_example'];
|
||||
|
||||
/**
|
||||
* The installation profile to use with this test.
|
||||
*
|
||||
* We need the 'minimal' profile in order to make sure the Tool block is
|
||||
* available.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $profile = 'minimal';
|
||||
|
||||
/**
|
||||
* Various functional test of the Config Entity Example module.
|
||||
*
|
||||
* 1) Verify that the Marvin entity was created when the module was installed.
|
||||
*
|
||||
* 2) Verify that permissions are applied to the various defined paths.
|
||||
*
|
||||
* 3) Verify that we can manage entities through the user interface.
|
||||
*
|
||||
* 4) Verify that the entity we add can be re-edited.
|
||||
*
|
||||
* 5) Verify that the label is shown in the list.
|
||||
*/
|
||||
public function testConfigEntityExample() {
|
||||
$assert = $this->assertSession();
|
||||
|
||||
// 1) Verify that the Marvin entity was created when the module was
|
||||
// installed.
|
||||
$entity = Robot::load('marvin');
|
||||
$this->assertNotNull($entity, 'Marvin was created during installation.');
|
||||
|
||||
// 2) Verify that permissions are applied to the various defined paths.
|
||||
// Define some paths. Since the Marvin entity is defined, we can use it
|
||||
// in our management paths.
|
||||
$forbidden_paths = [
|
||||
'/examples/config-entity-example',
|
||||
'/examples/config-entity-example/add',
|
||||
'/examples/config-entity-example/manage/marvin',
|
||||
'/examples/config-entity-example/manage/marvin/delete',
|
||||
];
|
||||
// Check each of the paths to make sure we don't have access. At this point
|
||||
// we haven't logged in any users, so the client is anonymous.
|
||||
foreach ($forbidden_paths as $path) {
|
||||
$this->drupalGet($path);
|
||||
$assert->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
// Create a user with no permissions.
|
||||
$noperms_user = $this->drupalCreateUser();
|
||||
$this->drupalLogin($noperms_user);
|
||||
// Should be the same result for forbidden paths, since the user needs
|
||||
// special permissions for these paths.
|
||||
foreach ($forbidden_paths as $path) {
|
||||
$this->drupalGet($path);
|
||||
$assert->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
// Create a user who can administer robots.
|
||||
$admin_user = $this->drupalCreateUser(['administer robots']);
|
||||
$this->drupalLogin($admin_user);
|
||||
// Forbidden paths aren't forbidden any more.
|
||||
foreach ($forbidden_paths as $unforbidden) {
|
||||
$this->drupalGet($unforbidden);
|
||||
$assert->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
// Now that we have the admin user logged in, check the menu links.
|
||||
$this->drupalGet('');
|
||||
$assert->linkByHrefExists('examples/config-entity-example');
|
||||
|
||||
// 3) Verify that we can manage entities through the user interface.
|
||||
// We still have the admin user logged in, so we'll create, update, and
|
||||
// delete an entity.
|
||||
// Go to the list page.
|
||||
$this->drupalGet('/examples/config-entity-example');
|
||||
$this->clickLink('Add robot');
|
||||
$robot_machine_name = 'roboname';
|
||||
$this->drupalPostForm(
|
||||
NULL,
|
||||
[
|
||||
'label' => $robot_machine_name,
|
||||
'id' => $robot_machine_name,
|
||||
'floopy' => TRUE,
|
||||
],
|
||||
'Create Robot'
|
||||
);
|
||||
|
||||
// 4) Verify that our robot appears when we edit it.
|
||||
$this->drupalGet('/examples/config-entity-example/manage/' . $robot_machine_name);
|
||||
$assert->fieldExists('label');
|
||||
$assert->checkboxChecked('edit-floopy');
|
||||
|
||||
// 5) Verify that the label and machine name are shown in the list.
|
||||
$this->drupalGet('/examples/config-entity-example');
|
||||
$this->clickLink('Add robot');
|
||||
$robby_machine_name = 'robby_machine_name';
|
||||
$robby_label = 'Robby label';
|
||||
$this->drupalPostForm(
|
||||
NULL,
|
||||
[
|
||||
'label' => $robby_label,
|
||||
'id' => $robby_machine_name,
|
||||
'floopy' => TRUE,
|
||||
],
|
||||
'Create Robot'
|
||||
);
|
||||
$this->drupalGet('/examples/config-entity-example');
|
||||
$assert->pageTextContains($robby_label);
|
||||
$assert->pageTextContains($robby_machine_name);
|
||||
|
||||
// 6) Verify that required links are present on respective paths.
|
||||
$this->assertLinkByHref('/examples/config-entity-example/add');
|
||||
$this->assertLinkByHref('/examples/config-entity-example/manage/robby_machine_name');
|
||||
$this->assertLinkByHref('/examples/config-entity-example/manage/robby_machine_name/delete');
|
||||
|
||||
// Verify links on Add Robot.
|
||||
$this->drupalGet('/examples/config-entity-example/add');
|
||||
$this->assertActionButton('examples/config-entity-example');
|
||||
|
||||
// Verify links on Edit Robot.
|
||||
$this->drupalGet('/examples/config-entity-example/manage/robby_machine_name');
|
||||
$this->assertLinkByHref('/examples/config-entity-example/manage/robby_machine_name/delete');
|
||||
$this->assertActionButton('examples/config-entity-example');
|
||||
|
||||
// Verify links on Delete Robot.
|
||||
$this->drupalGet('/examples/config-entity-example/manage/robby_machine_name/delete');
|
||||
// List page will be the destination of the cancel link.
|
||||
$cancel_button = $this->xpath(
|
||||
'//a[@id="edit-cancel" and contains(@href, :path)]',
|
||||
[':path' => '/examples/config-entity-example']
|
||||
);
|
||||
$this->assertEqual(count($cancel_button), 1, 'Found cancel button linking to list page.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an assertion for the action button.
|
||||
*
|
||||
* @param string $path
|
||||
* Drupal path to a page.
|
||||
*/
|
||||
protected function assertActionButton($path) {
|
||||
$button_element = $this->xpath(
|
||||
'//a[contains(@class, "button-action") and contains(@data-drupal-link-system-path, :path)]',
|
||||
[':path' => $path]
|
||||
);
|
||||
$this->assertEqual(count($button_element), 1, 'Found action button for path: ' . $path);
|
||||
}
|
||||
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
name: Content Entity Example
|
||||
type: module
|
||||
description: Demonstrates how to create a content entity.
|
||||
package: Example modules
|
||||
# core: 8.x
|
||||
# These modules are required by the tests, must be available at bootstrap time
|
||||
dependencies:
|
||||
- drupal:options
|
||||
- examples:examples
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.x-dev'
|
||||
core: '8.x'
|
||||
project: 'examples'
|
||||
datestamp: 1513537386
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
# All action links for this module
|
||||
|
||||
content_entity_example.contact_add:
|
||||
# Which route will be called by the link
|
||||
route_name: content_entity_example.contact_add
|
||||
title: 'Add contact'
|
||||
|
||||
# Where will the link appear, defined by route name.
|
||||
appears_on:
|
||||
- entity.content_entity_example_contact.collection
|
||||
- entity.content_entity_example_contact.canonical
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
# Define the menu links for this module
|
||||
|
||||
entity.content_entity_example_contact.collection:
|
||||
title: 'Content Entity Example: Contacts listing'
|
||||
route_name: entity.content_entity_example_contact.collection
|
||||
description: 'List contacts'
|
||||
weight: 10
|
||||
content_entity_example_contact.admin.structure.settings:
|
||||
title: 'Contact settings'
|
||||
description: 'Configure contact entity'
|
||||
route_name: content_entity_example.contact_settings
|
||||
parent: system.admin_structure
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
# Define the 'local' links for the module
|
||||
|
||||
contact.settings_tab:
|
||||
route_name: content_entity_example.contact_settings
|
||||
title: Settings
|
||||
base_route: content_entity_example.contact_settings
|
||||
|
||||
contact.view:
|
||||
route_name: entity.content_entity_example_contact.canonical
|
||||
base_route: entity.content_entity_example_contact.canonical
|
||||
title: View
|
||||
|
||||
contact.page_edit:
|
||||
route_name: entity.content_entity_example_contact.edit_form
|
||||
base_route: entity.content_entity_example_contact.canonical
|
||||
title: Edit
|
||||
|
||||
contact.delete_confirm:
|
||||
route_name: entity.content_entity_example_contact.delete_form
|
||||
base_route: entity.content_entity_example_contact.canonical
|
||||
title: Delete
|
||||
weight: 10
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains Drupal\content_entity_example\content_entity_example.module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup content_entity_example Example: Content Entity
|
||||
* @ingroup examples
|
||||
* @{
|
||||
* Implement a content entity.
|
||||
*
|
||||
* This module demonstrates implementing a content entity.
|
||||
*
|
||||
* Entity API is the API that stores pieces of content and configuration for
|
||||
* Drupal core. For instance, if you've encountered node content types, you've
|
||||
* encountered entities of type 'node.'
|
||||
*
|
||||
* This example implements the Entity API so that we have an entity type usable
|
||||
* by the user which you might think of as specialized nodes, but which are
|
||||
* different from nodes. These entities are called Contact, and are known
|
||||
* internally by the machine name content_entity_example_contact.
|
||||
*
|
||||
* Contact is a fieldable content entity used to hold structured information
|
||||
* without the overhead of using a node content type. 'Fieldable' means you can
|
||||
* attach fields to it, like you can with nodes. It is defined programmatically
|
||||
* (completely in code). We will show the main techniques to handle and expose
|
||||
* the contents of this entity type.
|
||||
*
|
||||
* The Contact entity will demonstrate the main tasks for an entity:
|
||||
* - define
|
||||
* - save
|
||||
* - load
|
||||
* - view
|
||||
* - edit
|
||||
* - delete
|
||||
* - control access
|
||||
*
|
||||
* Where ever possible, we use the amazing tools built into D8 natively.
|
||||
*
|
||||
* @see Drupal\content_entity_example\Entity\Contact
|
||||
* @see config_entity_example
|
||||
* }
|
||||
*/
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
'delete contact entity':
|
||||
title: Delete entity content.
|
||||
'add contact entity':
|
||||
title: Add entity content
|
||||
'view contact entity':
|
||||
title: View entity content
|
||||
'edit contact entity':
|
||||
title: Edit entity content
|
||||
'administer contact entity':
|
||||
title: Administer settings
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
# This file brings everything together. Very nifty!
|
||||
|
||||
# Route name can be used in several places; e.g. links, redirects, and local
|
||||
# actions.
|
||||
entity.content_entity_example_contact.canonical:
|
||||
path: '/content_entity_example_contact/{content_entity_example_contact}'
|
||||
defaults:
|
||||
# Calls the view controller, defined in the annotation of the contact entity
|
||||
_entity_view: 'content_entity_example_contact'
|
||||
_title: 'Contact content'
|
||||
requirements:
|
||||
# Calls the access controller of the entity, $operation 'view'
|
||||
_entity_access: 'content_entity_example_contact.view'
|
||||
|
||||
entity.content_entity_example_contact.collection:
|
||||
path: '/content_entity_example_contact/list'
|
||||
defaults:
|
||||
# Calls the list controller, defined in the annotation of the contact entity.
|
||||
_entity_list: 'content_entity_example_contact'
|
||||
_title: 'Contact list'
|
||||
requirements:
|
||||
# Checks for permission directly.
|
||||
_permission: 'view contact entity'
|
||||
|
||||
content_entity_example.contact_add:
|
||||
path: '/content_entity_example_contact/add'
|
||||
defaults:
|
||||
# Calls the form.add controller, defined in the contact entity.
|
||||
_entity_form: content_entity_example_contact.add
|
||||
_title: 'Add contact'
|
||||
requirements:
|
||||
_entity_create_access: 'content_entity_example_contact'
|
||||
|
||||
entity.content_entity_example_contact.edit_form:
|
||||
path: '/content_entity_example_contact/{content_entity_example_contact}/edit'
|
||||
defaults:
|
||||
# Calls the form.edit controller, defined in the contact entity.
|
||||
_entity_form: content_entity_example_contact.edit
|
||||
_title: 'Edit contact'
|
||||
requirements:
|
||||
_entity_access: 'content_entity_example_contact.edit'
|
||||
|
||||
entity.content_entity_example_contact.delete_form:
|
||||
path: '/contact/{content_entity_example_contact}/delete'
|
||||
defaults:
|
||||
# Calls the form.delete controller, defined in the contact entity.
|
||||
_entity_form: content_entity_example_contact.delete
|
||||
_title: 'Delete contact'
|
||||
requirements:
|
||||
_entity_access: 'content_entity_example_contact.delete'
|
||||
|
||||
content_entity_example.contact_settings:
|
||||
path: 'admin/structure/content_entity_example_contact_settings'
|
||||
defaults:
|
||||
_form: '\Drupal\content_entity_example\Form\ContactSettingsForm'
|
||||
_title: 'Contact settings'
|
||||
requirements:
|
||||
_permission: 'administer contact entity'
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_entity_example;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Access controller for the comment entity.
|
||||
*
|
||||
* @see \Drupal\comment\Entity\Comment.
|
||||
*/
|
||||
class ContactAccessControlHandler extends EntityAccessControlHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Link the activities to the permissions. checkAccess is called with the
|
||||
* $operation as defined in the routing.yml file.
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
return AccessResult::allowedIfHasPermission($account, 'view contact entity');
|
||||
|
||||
case 'edit':
|
||||
return AccessResult::allowedIfHasPermission($account, 'edit contact entity');
|
||||
|
||||
case 'delete':
|
||||
return AccessResult::allowedIfHasPermission($account, 'delete contact entity');
|
||||
}
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Separate from the checkAccess because the entity does not yet exist, it
|
||||
* will be created during the 'add' process.
|
||||
*/
|
||||
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
|
||||
return AccessResult::allowedIfHasPermission($account, 'add contact entity');
|
||||
}
|
||||
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_entity_example;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\user\EntityOwnerInterface;
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a Contact entity.
|
||||
*
|
||||
* We have this interface so we can join the other interfaces it extends.
|
||||
*
|
||||
* @ingroup content_entity_example
|
||||
*/
|
||||
interface ContactInterface extends ContentEntityInterface, EntityOwnerInterface, EntityChangedInterface {
|
||||
|
||||
}
|
||||
-350
@@ -1,350 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_entity_example\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\content_entity_example\ContactInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\Core\Entity\EntityChangedTrait;
|
||||
|
||||
/**
|
||||
* Defines the ContentEntityExample entity.
|
||||
*
|
||||
* @ingroup content_entity_example
|
||||
*
|
||||
* This is the main definition of the entity type. From it, an entityType is
|
||||
* derived. The most important properties in this example are listed below.
|
||||
*
|
||||
* id: The unique identifier of this entityType. It follows the pattern
|
||||
* 'moduleName_xyz' to avoid naming conflicts.
|
||||
*
|
||||
* label: Human readable name of the entity type.
|
||||
*
|
||||
* handlers: Handler classes are used for different tasks. You can use
|
||||
* standard handlers provided by D8 or build your own, most probably derived
|
||||
* from the standard class. In detail:
|
||||
*
|
||||
* - view_builder: we use the standard controller to view an instance. It is
|
||||
* called when a route lists an '_entity_view' default for the entityType
|
||||
* (see routing.yml for details. The view can be manipulated by using the
|
||||
* standard drupal tools in the settings.
|
||||
*
|
||||
* - list_builder: We derive our own list builder class from the
|
||||
* entityListBuilder to control the presentation.
|
||||
* If there is a view available for this entity from the views module, it
|
||||
* overrides the list builder. @todo: any view? naming convention?
|
||||
*
|
||||
* - form: We derive our own forms to add functionality like additional fields,
|
||||
* redirects etc. These forms are called when the routing list an
|
||||
* '_entity_form' default for the entityType. Depending on the suffix
|
||||
* (.add/.edit/.delete) in the route, the correct form is called.
|
||||
*
|
||||
* - access: Our own accessController where we determine access rights based on
|
||||
* permissions.
|
||||
*
|
||||
* More properties:
|
||||
*
|
||||
* - base_table: Define the name of the table used to store the data. Make sure
|
||||
* it is unique. The schema is automatically determined from the
|
||||
* BaseFieldDefinitions below. The table is automatically created during
|
||||
* installation.
|
||||
*
|
||||
* - fieldable: Can additional fields be added to the entity via the GUI?
|
||||
* Analog to content types.
|
||||
*
|
||||
* - entity_keys: How to access the fields. Analog to 'nid' or 'uid'.
|
||||
*
|
||||
* - links: Provide links to do standard tasks. The 'edit-form' and
|
||||
* 'delete-form' links are added to the list built by the
|
||||
* entityListController. They will show up as action buttons in an additional
|
||||
* column.
|
||||
*
|
||||
* There are many more properties to be used in an entity type definition. For
|
||||
* a complete overview, please refer to the '\Drupal\Core\Entity\EntityType'
|
||||
* class definition.
|
||||
*
|
||||
* The following construct is the actual definition of the entity type which
|
||||
* is read and cached. Don't forget to clear cache after changes.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "content_entity_example_contact",
|
||||
* label = @Translation("Contact entity"),
|
||||
* handlers = {
|
||||
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
|
||||
* "list_builder" = "Drupal\content_entity_example\Entity\Controller\ContactListBuilder",
|
||||
* "form" = {
|
||||
* "add" = "Drupal\content_entity_example\Form\ContactForm",
|
||||
* "edit" = "Drupal\content_entity_example\Form\ContactForm",
|
||||
* "delete" = "Drupal\content_entity_example\Form\ContactDeleteForm",
|
||||
* },
|
||||
* "access" = "Drupal\content_entity_example\ContactAccessControlHandler",
|
||||
* },
|
||||
* list_cache_contexts = { "user" },
|
||||
* base_table = "contact",
|
||||
* admin_permission = "administer content_entity_example entity",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "name",
|
||||
* "uuid" = "uuid"
|
||||
* },
|
||||
* links = {
|
||||
* "canonical" = "/content_entity_example_contact/{content_entity_example_contact}",
|
||||
* "edit-form" = "/content_entity_example_contact/{content_entity_example_contact}/edit",
|
||||
* "delete-form" = "/contact/{content_entity_example_contact}/delete",
|
||||
* "collection" = "/content_entity_example_contact/list"
|
||||
* },
|
||||
* field_ui_base_route = "content_entity_example.contact_settings",
|
||||
* )
|
||||
*
|
||||
* The 'links' above are defined by their path. For core to find the
|
||||
* corresponding route, the route name must follow the correct pattern:
|
||||
*
|
||||
* entity.<entity-name>.<link-name> (replace dashes with underscores)
|
||||
* Example: 'entity.content_entity_example_contact.canonical'
|
||||
*
|
||||
* See routing file above for the corresponding implementation
|
||||
*
|
||||
* The Contact class defines methods and fields for the contact entity.
|
||||
*
|
||||
* Being derived from the ContentEntityBase class, we can override the methods
|
||||
* we want. In our case we want to provide access to the standard fields about
|
||||
* creation and changed time stamps.
|
||||
*
|
||||
* Our interface (see ContactInterface) also exposes the EntityOwnerInterface.
|
||||
* This allows us to provide methods for setting and providing ownership
|
||||
* information.
|
||||
*
|
||||
* The most important part is the definitions of the field properties for this
|
||||
* entity type. These are of the same type as fields added through the GUI, but
|
||||
* they can by changed in code. In the definition we can define if the user with
|
||||
* the rights privileges can influence the presentation (view, edit) of each
|
||||
* field.
|
||||
*
|
||||
* The class also uses the EntityChangedTrait trait which allows it to record
|
||||
* timestamps of save operations.
|
||||
*/
|
||||
class Contact extends ContentEntityBase implements ContactInterface {
|
||||
|
||||
use EntityChangedTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* When a new entity instance is added, set the user_id entity reference to
|
||||
* the current user as the creator of the instance.
|
||||
*/
|
||||
public static function preCreate(EntityStorageInterface $storage_controller, array &$values) {
|
||||
parent::preCreate($storage_controller, $values);
|
||||
$values += [
|
||||
'user_id' => \Drupal::currentUser()->id(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCreatedTime() {
|
||||
return $this->get('created')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChangedTime() {
|
||||
return $this->get('changed')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOwner() {
|
||||
return $this->get('user_id')->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOwnerId() {
|
||||
return $this->get('user_id')->target_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setOwnerId($uid) {
|
||||
$this->set('user_id', $uid);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setOwner(UserInterface $account) {
|
||||
$this->set('user_id', $account->id());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Define the field properties here.
|
||||
*
|
||||
* Field name, type and size determine the table structure.
|
||||
*
|
||||
* In addition, we can define how the field and its content can be manipulated
|
||||
* in the GUI. The behaviour of the widgets used can be determined here.
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
|
||||
// Standard field, used as unique if primary index.
|
||||
$fields['id'] = BaseFieldDefinition::create('integer')
|
||||
->setLabel(t('ID'))
|
||||
->setDescription(t('The ID of the Contact entity.'))
|
||||
->setReadOnly(TRUE);
|
||||
|
||||
// Standard field, unique outside of the scope of the current project.
|
||||
$fields['uuid'] = BaseFieldDefinition::create('uuid')
|
||||
->setLabel(t('UUID'))
|
||||
->setDescription(t('The UUID of the Contact entity.'))
|
||||
->setReadOnly(TRUE);
|
||||
|
||||
// Name field for the contact.
|
||||
// We set display options for the view as well as the form.
|
||||
// Users with correct privileges can change the view and edit configuration.
|
||||
$fields['name'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Name'))
|
||||
->setDescription(t('The name of the Contact entity.'))
|
||||
->setSettings([
|
||||
'max_length' => 255,
|
||||
'text_processing' => 0,
|
||||
])
|
||||
// Set no default value.
|
||||
->setDefaultValue(NULL)
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'above',
|
||||
'type' => 'string',
|
||||
'weight' => -6,
|
||||
])
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'string_textfield',
|
||||
'weight' => -6,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE)
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
$fields['first_name'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('First Name'))
|
||||
->setDescription(t('The first name of the Contact entity.'))
|
||||
->setSettings([
|
||||
'max_length' => 255,
|
||||
'text_processing' => 0,
|
||||
])
|
||||
// Set no default value.
|
||||
->setDefaultValue(NULL)
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'above',
|
||||
'type' => 'string',
|
||||
'weight' => -5,
|
||||
])
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'string_textfield',
|
||||
'weight' => -5,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE)
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
// Gender field for the contact.
|
||||
// ListTextType with a drop down menu widget.
|
||||
// The values shown in the menu are 'male' and 'female'.
|
||||
// In the view the field content is shown as string.
|
||||
// In the form the choices are presented as options list.
|
||||
$fields['gender'] = BaseFieldDefinition::create('list_string')
|
||||
->setLabel(t('Gender'))
|
||||
->setDescription(t('The gender of the Contact entity.'))
|
||||
->setSettings([
|
||||
'allowed_values' => [
|
||||
'female' => 'female',
|
||||
'male' => 'male',
|
||||
],
|
||||
])
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'above',
|
||||
'type' => 'string',
|
||||
'weight' => -4,
|
||||
])
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'options_select',
|
||||
'weight' => -4,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE)
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
// Owner field of the contact.
|
||||
// Entity reference field, holds the reference to the user object.
|
||||
// The view shows the user name field of the user.
|
||||
// The form presents a auto complete field for the user name.
|
||||
$fields['user_id'] = BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(t('User Name'))
|
||||
->setDescription(t('The Name of the associated user.'))
|
||||
->setSetting('target_type', 'user')
|
||||
->setSetting('handler', 'default')
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'above',
|
||||
'type' => 'author',
|
||||
'weight' => -3,
|
||||
])
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'entity_reference_autocomplete',
|
||||
'settings' => [
|
||||
'match_operator' => 'CONTAINS',
|
||||
'size' => 60,
|
||||
'placeholder' => '',
|
||||
],
|
||||
'weight' => -3,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE)
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
// Role field for the contact.
|
||||
// The values shown in options are 'administrator' and 'user'.
|
||||
$fields['role'] = BaseFieldDefinition::create('list_string')
|
||||
->setLabel(t('Role'))
|
||||
->setDescription(t('The role of the Contact entity.'))
|
||||
->setSettings([
|
||||
'allowed_values' => [
|
||||
'administrator' => 'administrator',
|
||||
'user' => 'user',
|
||||
],
|
||||
])
|
||||
// Set the default value of this field to 'user'.
|
||||
->setDefaultValue('user')
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'above',
|
||||
'type' => 'string',
|
||||
'weight' => -2,
|
||||
])
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'options_select',
|
||||
'weight' => -2,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE)
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
$fields['langcode'] = BaseFieldDefinition::create('language')
|
||||
->setLabel(t('Language code'))
|
||||
->setDescription(t('The language code of ContentEntityExample entity.'));
|
||||
$fields['created'] = BaseFieldDefinition::create('created')
|
||||
->setLabel(t('Created'))
|
||||
->setDescription(t('The time that the entity was created.'));
|
||||
|
||||
$fields['changed'] = BaseFieldDefinition::create('changed')
|
||||
->setLabel(t('Changed'))
|
||||
->setDescription(t('The time that the entity was last edited.'));
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_entity_example\Entity\Controller;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityListBuilder;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Routing\UrlGeneratorInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a list controller for content_entity_example entity.
|
||||
*
|
||||
* @ingroup content_entity_example
|
||||
*/
|
||||
class ContactListBuilder extends EntityListBuilder {
|
||||
|
||||
/**
|
||||
* The url generator.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\UrlGeneratorInterface
|
||||
*/
|
||||
protected $urlGenerator;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('entity.manager')->getStorage($entity_type->id()),
|
||||
$container->get('url_generator')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new ContactListBuilder object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The entity storage class.
|
||||
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
|
||||
* The url generator.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, UrlGeneratorInterface $url_generator) {
|
||||
parent::__construct($entity_type, $storage);
|
||||
$this->urlGenerator = $url_generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* We override ::render() so that we can add our own content above the table.
|
||||
* parent::render() is where EntityListBuilder creates the table using our
|
||||
* buildHeader() and buildRow() implementations.
|
||||
*/
|
||||
public function render() {
|
||||
$build['description'] = [
|
||||
'#markup' => $this->t('Content Entity Example implements a Contacts model. These contacts are fieldable entities. You can manage the fields on the <a href="@adminlink">Contacts admin page</a>.', [
|
||||
'@adminlink' => $this->urlGenerator->generateFromRoute('content_entity_example.contact_settings'),
|
||||
]),
|
||||
];
|
||||
$build['table'] = parent::render();
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Building the header and content lines for the contact list.
|
||||
*
|
||||
* Calling the parent::buildHeader() adds a column for the possible actions
|
||||
* and inserts the 'edit' and 'delete' links as defined for the entity type.
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['id'] = $this->t('ContactID');
|
||||
$header['name'] = $this->t('Name');
|
||||
$header['first_name'] = $this->t('First Name');
|
||||
$header['gender'] = $this->t('Gender');
|
||||
$header['role'] = $this->t('Role');
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
/* @var $entity \Drupal\content_entity_example\Entity\Contact */
|
||||
$row['id'] = $entity->id();
|
||||
$row['name'] = $entity->link();
|
||||
$row['first_name'] = $entity->first_name->value;
|
||||
$row['gender'] = $entity->gender->value;
|
||||
$row['role'] = $entity->role->value;
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_entity_example\Form;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a form for deleting a content_entity_example entity.
|
||||
*
|
||||
* @ingroup content_entity_example
|
||||
*/
|
||||
class ContactDeleteForm extends ContentEntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete entity %name?', ['%name' => $this->entity->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* If the delete command is canceled, return to the contact list.
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.content_entity_example_contact.collection');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Delete the entity and log the event. logger() replaces the watchdog.
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$entity = $this->getEntity();
|
||||
$entity->delete();
|
||||
|
||||
$this->logger('content_entity_example')->notice('@type: deleted %title.',
|
||||
[
|
||||
'@type' => $this->entity->bundle(),
|
||||
'%title' => $this->entity->label(),
|
||||
]);
|
||||
$form_state->setRedirect('entity.content_entity_example_contact.collection');
|
||||
}
|
||||
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_entity_example\Form;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Form controller for the content_entity_example entity edit forms.
|
||||
*
|
||||
* @ingroup content_entity_example
|
||||
*/
|
||||
class ContactForm extends ContentEntityForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
/* @var $entity \Drupal\content_entity_example\Entity\Contact */
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
$entity = $this->entity;
|
||||
|
||||
$form['langcode'] = [
|
||||
'#title' => $this->t('Language'),
|
||||
'#type' => 'language_select',
|
||||
'#default_value' => $entity->getUntranslated()->language()->getId(),
|
||||
'#languages' => Language::STATE_ALL,
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$form_state->setRedirect('entity.content_entity_example_contact.collection');
|
||||
$entity = $this->getEntity();
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_entity_example\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Class ContentEntityExampleSettingsForm.
|
||||
*
|
||||
* @ingroup content_entity_example
|
||||
*/
|
||||
class ContactSettingsForm extends FormBase {
|
||||
/**
|
||||
* Returns a unique string identifying the form.
|
||||
*
|
||||
* @return string
|
||||
* The unique string identifying the form.
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'content_entity_example_settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Empty implementation of the abstract submit class.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['contact_settings']['#markup'] = 'Settings form for ContentEntityExample. Manage field settings here.';
|
||||
return $form;
|
||||
}
|
||||
|
||||
}
|
||||
-249
@@ -1,249 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\content_entity_example\Tests;
|
||||
|
||||
use Drupal\content_entity_example\Entity\Contact;
|
||||
use Drupal\Tests\examples\Functional\ExamplesBrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the basic functions of the Content Entity Example module.
|
||||
*
|
||||
* @ingroup content_entity_example
|
||||
*
|
||||
* @group content_entity_example
|
||||
* @group examples
|
||||
*/
|
||||
class ContentEntityExampleTest extends ExamplesBrowserTestBase {
|
||||
|
||||
public static $modules = ['content_entity_example', 'block', 'field_ui'];
|
||||
|
||||
/**
|
||||
* Basic tests for Content Entity Example.
|
||||
*/
|
||||
public function testContentEntityExample() {
|
||||
$assert = $this->assertSession();
|
||||
|
||||
$web_user = $this->drupalCreateUser([
|
||||
'add contact entity',
|
||||
'edit contact entity',
|
||||
'view contact entity',
|
||||
'delete contact entity',
|
||||
'administer contact entity',
|
||||
'administer content_entity_example_contact display',
|
||||
'administer content_entity_example_contact fields',
|
||||
'administer content_entity_example_contact form display',
|
||||
]);
|
||||
|
||||
// Anonymous User should not see the link to the listing.
|
||||
$assert->pageTextNotContains('Content Entity Example: Contacts Listing');
|
||||
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Web_user user has the right to view listing.
|
||||
$assert->linkExists('Content Entity Example: Contacts Listing');
|
||||
|
||||
$this->clickLink('Content Entity Example: Contacts Listing');
|
||||
|
||||
// WebUser can add entity content.
|
||||
$assert->linkExists('Add Contact');
|
||||
|
||||
$this->clickLink(t('Add Contact'));
|
||||
|
||||
$assert->fieldValueEquals('name[0][value]', '');
|
||||
$assert->fieldValueEquals('name[0][value]', '');
|
||||
$assert->fieldValueEquals('name[0][value]', '');
|
||||
$assert->fieldValueEquals('name[0][value]', '');
|
||||
|
||||
$user_ref = $web_user->name->value . ' (' . $web_user->id() . ')';
|
||||
$assert->fieldValueEquals('user_id[0][target_id]', $user_ref);
|
||||
|
||||
// Post content, save an instance. Go back to list after saving.
|
||||
$edit = [
|
||||
'name[0][value]' => 'test name',
|
||||
'first_name[0][value]' => 'test first name',
|
||||
'gender' => 'male',
|
||||
'role' => 'administrator',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
|
||||
// Entity listed.
|
||||
$assert->linkExists('Edit');
|
||||
$assert->linkExists('Delete');
|
||||
|
||||
$this->clickLink('test name');
|
||||
|
||||
// Entity shown.
|
||||
$assert->pageTextContains('test name');
|
||||
$assert->pageTextContains('test first name');
|
||||
$assert->pageTextContains('administrator');
|
||||
$assert->pageTextContains('male');
|
||||
$assert->linkExists('Add Contact');
|
||||
$assert->linkExists('Edit');
|
||||
$assert->linkExists('Delete');
|
||||
|
||||
// Delete the entity.
|
||||
$this->clickLink('Delete');
|
||||
|
||||
// Confirm deletion.
|
||||
$assert->linkExists('Cancel');
|
||||
$this->drupalPostForm(NULL, [], 'Delete');
|
||||
|
||||
// Back to list, must be empty.
|
||||
$assert->pageTextNotContains('test name');
|
||||
|
||||
// Settings page.
|
||||
$this->drupalGet('admin/structure/content_entity_example_contact_settings');
|
||||
$assert->pageTextContains('Contact Settings');
|
||||
|
||||
// Make sure the field manipulation links are available.
|
||||
$assert->linkExists('Settings');
|
||||
$assert->linkExists('Manage fields');
|
||||
$assert->linkExists('Manage form display');
|
||||
$assert->linkExists('Manage display');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test all paths exposed by the module, by permission.
|
||||
*/
|
||||
public function testPaths() {
|
||||
$assert = $this->assertSession();
|
||||
|
||||
// Generate a contact so that we can test the paths against it.
|
||||
$contact = Contact::create(
|
||||
[
|
||||
'name' => 'somename',
|
||||
'first_name' => 'Joe',
|
||||
'gender' => 'female',
|
||||
'role' => 'administrator',
|
||||
]
|
||||
);
|
||||
$contact->save();
|
||||
|
||||
// Gather the test data.
|
||||
$data = $this->providerTestPaths($contact->id());
|
||||
|
||||
// Run the tests.
|
||||
foreach ($data as $datum) {
|
||||
// drupalCreateUser() doesn't know what to do with an empty permission
|
||||
// array, so we help it out.
|
||||
if ($datum[2]) {
|
||||
$user = $this->drupalCreateUser([$datum[2]]);
|
||||
$this->drupalLogin($user);
|
||||
}
|
||||
else {
|
||||
$user = $this->drupalCreateUser();
|
||||
$this->drupalLogin($user);
|
||||
}
|
||||
$this->drupalGet($datum[1]);
|
||||
$assert->statusCodeEquals($datum[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testPaths.
|
||||
*
|
||||
* @param int $contact_id
|
||||
* The id of an existing Contact entity.
|
||||
*
|
||||
* @return array
|
||||
* Nested array of testing data. Arranged like this:
|
||||
* - Expected response code.
|
||||
* - Path to request.
|
||||
* - Permission for the user.
|
||||
*/
|
||||
protected function providerTestPaths($contact_id) {
|
||||
return [
|
||||
[
|
||||
200,
|
||||
'/content_entity_example_contact/' . $contact_id,
|
||||
'view contact entity',
|
||||
],
|
||||
[
|
||||
403,
|
||||
'/content_entity_example_contact/' . $contact_id,
|
||||
'',
|
||||
],
|
||||
[
|
||||
200,
|
||||
'/content_entity_example_contact/list',
|
||||
'view contact entity',
|
||||
],
|
||||
[
|
||||
403,
|
||||
'/content_entity_example_contact/list',
|
||||
'',
|
||||
],
|
||||
[
|
||||
200,
|
||||
'/content_entity_example_contact/add',
|
||||
'add contact entity',
|
||||
],
|
||||
[
|
||||
403,
|
||||
'/content_entity_example_contact/add',
|
||||
'',
|
||||
],
|
||||
[
|
||||
200,
|
||||
'/content_entity_example_contact/' . $contact_id . '/edit',
|
||||
'edit contact entity',
|
||||
],
|
||||
[
|
||||
403,
|
||||
'/content_entity_example_contact/' . $contact_id . '/edit',
|
||||
'',
|
||||
],
|
||||
[
|
||||
200,
|
||||
'/contact/' . $contact_id . '/delete',
|
||||
'delete contact entity',
|
||||
],
|
||||
[
|
||||
403,
|
||||
'/contact/' . $contact_id . '/delete',
|
||||
'',
|
||||
],
|
||||
[
|
||||
200,
|
||||
'admin/structure/content_entity_example_contact_settings',
|
||||
'administer contact entity',
|
||||
],
|
||||
[
|
||||
403,
|
||||
'admin/structure/content_entity_example_contact_settings',
|
||||
'',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test add new fields to the contact entity.
|
||||
*/
|
||||
public function testAddFields() {
|
||||
$web_user = $this->drupalCreateUser([
|
||||
'administer contact entity',
|
||||
'administer content_entity_example_contact display',
|
||||
'administer content_entity_example_contact fields',
|
||||
'administer content_entity_example_contact form display',
|
||||
]);
|
||||
|
||||
$this->drupalLogin($web_user);
|
||||
$entity_name = 'content_entity_example_contact';
|
||||
$add_field_url = 'admin/structure/' . $entity_name . '_settings/fields/add-field';
|
||||
$this->drupalGet($add_field_url);
|
||||
$field_name = 'test_name';
|
||||
$edit = [
|
||||
'new_storage_type' => 'list_string',
|
||||
'label' => 'test name',
|
||||
'field_name' => $field_name,
|
||||
];
|
||||
|
||||
$this->drupalPostForm(NULL, $edit, 'Save and continue');
|
||||
$expected_path = $this->buildUrl('admin/structure/' . $entity_name . '_settings/fields/' . $entity_name . '.' . $entity_name . '.field_' . $field_name . '/storage');
|
||||
|
||||
// Fetch url without query parameters.
|
||||
$current_path = strtok($this->getUrl(), '?');
|
||||
$this->assertEquals($expected_path, $current_path);
|
||||
}
|
||||
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
# Default form values for \Drupal\cron_example\Form\CronExampleForm.
|
||||
# @see examples/cron_example/config/schema/cron_example.schema.yml
|
||||
# @see \Drupal\cron_example\Form\CronExampleForm::getEditableConfigNames()
|
||||
interval: 300
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
# Set configuration defaults. This schema describes the cron_example.settings
|
||||
# config. The defaults for this config are set in
|
||||
# config/install/cron_example.settings.yml and then used by
|
||||
# \Drupal\cron_example\Form\CronExampleForm.
|
||||
# @see https://www.drupal.org/node/1905070
|
||||
cron_example.settings:
|
||||
type: config_object
|
||||
label: 'Cron Example settings'
|
||||
mapping:
|
||||
interval:
|
||||
type: integer
|
||||
label: 'Period between cron runs'
|
||||
@@ -1,14 +0,0 @@
|
||||
name: Cron Example
|
||||
type: module
|
||||
description: 'Demonstrates hook_cron() and related features'
|
||||
package: Example modules
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- drupal:node
|
||||
- examples:examples
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.x-dev'
|
||||
core: '8.x'
|
||||
project: 'examples'
|
||||
datestamp: 1513537386
|
||||
@@ -1,3 +0,0 @@
|
||||
cron_example.description:
|
||||
title: Cron Example
|
||||
route_name: cron_example
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Demonstrates use of the Cron API in Drupal - hook_cron().
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup cron_example Example: Cron
|
||||
* @ingroup examples
|
||||
* @{
|
||||
* Example using Cron API, including hook_cron() and @QueueWorker plugins
|
||||
*
|
||||
* This example is part of the Examples for Developers Project
|
||||
* which you can download and experiment with at
|
||||
* http://drupal.org/project/examples
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_cron().
|
||||
*
|
||||
* We implement hook_cron() to do "background" processing. It gets called every
|
||||
* time the Drupal cron runs. We then decide what has to happen in response.
|
||||
*
|
||||
* In this example, we log a message after the time given in the state value
|
||||
* 'cron_example.next_execution'. Then we update that variable to a time in the
|
||||
* future.
|
||||
*/
|
||||
function cron_example_cron() {
|
||||
// We access our configuration.
|
||||
$cron_config = \Drupal::configFactory()->getEditable('cron_example.settings');
|
||||
// Default to an hourly interval. Of course, cron has to be running at least
|
||||
// hourly for this to work.
|
||||
$interval = $cron_config->get('interval');
|
||||
$interval = !empty($interval) ? $interval : 3600;
|
||||
|
||||
// We usually don't want to act every time cron runs (which could be every
|
||||
// minute) so keep a time for the next run in the site state.
|
||||
$next_execution = \Drupal::state()->get('cron_example.next_execution');
|
||||
$next_execution = !empty($next_execution) ? $next_execution : 0;
|
||||
if (REQUEST_TIME >= $next_execution) {
|
||||
// This is a silly example of a cron job.
|
||||
// It just makes it obvious that the job has run without
|
||||
// making any changes to your database.
|
||||
\Drupal::logger('cron_example')->notice('cron_example ran');
|
||||
if (\Drupal::state()->get('cron_example_show_status_message')) {
|
||||
drupal_set_message(t('cron_example executed at %time', ['%time' => date_iso8601(REQUEST_TIME)]));
|
||||
\Drupal::state()->set('cron_example_show_status_message', FALSE);
|
||||
}
|
||||
\Drupal::state()->set('cron_example.next_execution', REQUEST_TIME + $interval);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup cron_example".
|
||||
*/
|
||||
@@ -1,7 +0,0 @@
|
||||
cron_example:
|
||||
path: '/examples/cron-example'
|
||||
defaults:
|
||||
_form: '\Drupal\cron_example\Form\CronExampleForm'
|
||||
_title: 'Cron Example'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
@@ -1,256 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\cron_example\Form;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\CronInterface;
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Queue\QueueFactory;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Form with examples on how to use cron.
|
||||
*/
|
||||
class CronExampleForm extends ConfigFormBase {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The cron service.
|
||||
*
|
||||
* @var \Drupal\Core\CronInterface
|
||||
*/
|
||||
protected $cron;
|
||||
|
||||
/**
|
||||
* The queue object.
|
||||
*
|
||||
* @var \Drupal\Core\Queue\QueueFactory
|
||||
*/
|
||||
protected $queue;
|
||||
|
||||
/**
|
||||
* The state keyvalue collection.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, AccountInterface $current_user, CronInterface $cron, QueueFactory $queue, StateInterface $state) {
|
||||
parent::__construct($config_factory);
|
||||
$this->currentUser = $current_user;
|
||||
$this->cron = $cron;
|
||||
$this->queue = $queue;
|
||||
$this->state = $state;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('config.factory'),
|
||||
$container->get('current_user'),
|
||||
$container->get('cron'),
|
||||
$container->get('queue'),
|
||||
$container->get('state')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'cron_example';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$config = $this->configFactory->get('cron_example.settings');
|
||||
|
||||
$form['status'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Cron status information'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
$form['status']['intro'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => $this->t('The cron example demonstrates hook_cron() and hook_queue_info() processing. If you have administrative privileges you can run cron from this page and see the results.'),
|
||||
];
|
||||
|
||||
$next_execution = \Drupal::state()->get('cron_example.next_execution');
|
||||
$next_execution = !empty($next_execution) ? $next_execution : REQUEST_TIME;
|
||||
|
||||
$args = [
|
||||
'%time' => date_iso8601(\Drupal::state()->get('cron_example.next_execution')),
|
||||
'%seconds' => $next_execution - REQUEST_TIME,
|
||||
];
|
||||
$form['status']['last'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => $this->t('cron_example_cron() will next execute the first time cron runs after %time (%seconds seconds from now)', $args),
|
||||
];
|
||||
|
||||
if ($this->currentUser->hasPermission('administer site configuration')) {
|
||||
$form['cron_run'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Run cron manually'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
$form['cron_run']['cron_reset'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t("Run cron_example's cron regardless of whether interval has expired."),
|
||||
'#default_value' => FALSE,
|
||||
];
|
||||
$form['cron_run']['cron_trigger']['actions'] = ['#type' => 'actions'];
|
||||
$form['cron_run']['cron_trigger']['actions']['sumbit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Run cron now'),
|
||||
'#submit' => [[$this, 'cronRun']],
|
||||
];
|
||||
}
|
||||
|
||||
$form['cron_queue_setup'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Cron queue setup (for hook_cron_queue_info(), etc.)'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
|
||||
$queue_1 = $this->queue->get('cron_example_queue_1');
|
||||
$queue_2 = $this->queue->get('cron_example_queue_2');
|
||||
|
||||
$args = [
|
||||
'%queue_1' => $queue_1->numberOfItems(),
|
||||
'%queue_2' => $queue_2->numberOfItems(),
|
||||
];
|
||||
$form['cron_queue_setup']['current_cron_queue_status'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => $this->t('There are currently %queue_1 items in queue 1 and %queue_2 items in queue 2', $args),
|
||||
];
|
||||
$form['cron_queue_setup']['num_items'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Number of items to add to queue'),
|
||||
'#options' => array_combine([1, 5, 10, 100, 1000], [1, 5, 10, 100, 1000]),
|
||||
'#default_value' => 5,
|
||||
];
|
||||
$form['cron_queue_setup']['queue'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Queue to add items to'),
|
||||
'#options' => [
|
||||
'cron_example_queue_1' => $this->t('Queue 1'),
|
||||
'cron_example_queue_2' => $this->t('Queue 2'),
|
||||
],
|
||||
'#default_value' => 'cron_example_queue_1',
|
||||
];
|
||||
$form['cron_queue_setup']['actions'] = ['#type' => 'actions'];
|
||||
$form['cron_queue_setup']['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Add jobs to queue'),
|
||||
'#submit' => [[$this, 'addItems']],
|
||||
];
|
||||
|
||||
$form['configuration'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Configuration of cron_example_cron()'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
$form['configuration']['cron_example_interval'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Cron interval'),
|
||||
'#description' => $this->t('Time after which cron_example_cron will respond to a processing request.'),
|
||||
'#default_value' => $config->get('interval'),
|
||||
'#options' => [
|
||||
60 => $this->t('1 minute'),
|
||||
300 => $this->t('5 minutes'),
|
||||
3600 => $this->t('1 hour'),
|
||||
86400 => $this->t('1 day'),
|
||||
],
|
||||
];
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow user to directly execute cron, optionally forcing it.
|
||||
*/
|
||||
public function cronRun(array &$form, FormStateInterface &$form_state) {
|
||||
$config = $this->configFactory->getEditable('cron_example.settings');
|
||||
|
||||
$cron_reset = $form_state->getValue('cron_reset');
|
||||
if (!empty($cron_reset)) {
|
||||
\Drupal::state()->set('cron_example.next_execution', 0);
|
||||
}
|
||||
|
||||
// Use a state variable to signal that cron was run manually from this form.
|
||||
$this->state->set('cron_example_show_status_message', TRUE);
|
||||
if ($this->cron->run()) {
|
||||
drupal_set_message($this->t('Cron ran successfully.'));
|
||||
}
|
||||
else {
|
||||
drupal_set_message($this->t('Cron run failed.'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the items to the queue when signaled by the form.
|
||||
*/
|
||||
public function addItems(array &$form, FormStateInterface &$form_state) {
|
||||
$values = $form_state->getValues();
|
||||
$queue_name = $form['cron_queue_setup']['queue'][$values['queue']]['#title'];
|
||||
$num_items = $form_state->getValue('num_items');
|
||||
// Queues are defined by a QueueWorker Plugin which are selected by their
|
||||
// id attritbute.
|
||||
// @see \Drupal\cron_example\Plugin\QueueWorker\ReportWorkerOne
|
||||
$queue = $this->queue->get($values['queue']);
|
||||
|
||||
for ($i = 1; $i <= $num_items; $i++) {
|
||||
// Create a new item, a new data object, which is passed to the
|
||||
// QueueWorker's processItem() method.
|
||||
$item = new \stdClass();
|
||||
$item->created = REQUEST_TIME;
|
||||
$item->sequence = $i;
|
||||
$queue->createItem($item);
|
||||
}
|
||||
|
||||
$args = [
|
||||
'%num' => $num_items,
|
||||
'%queue' => $queue_name,
|
||||
];
|
||||
drupal_set_message($this->t('Added %num items to %queue', $args));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Update the interval as stored in configuration. This will be read when
|
||||
// this modules hook_cron function fires and will be used to ensure that
|
||||
// action is taken only after the appropiate time has elapsed.
|
||||
$this->configFactory->getEditable('cron_example.settings')
|
||||
->set('interval', $form_state->getValue('cron_example_interval'))
|
||||
->save();
|
||||
|
||||
parent::submitForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return ['cron_example.settings'];
|
||||
}
|
||||
|
||||
}
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\cron_example\Plugin\QueueWorker;
|
||||
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Drupal\Core\Queue\QueueWorkerBase;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
|
||||
|
||||
/**
|
||||
* Provides base functionality for the ReportWorkers.
|
||||
*/
|
||||
abstract class ReportWorkerBase extends QueueWorkerBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
|
||||
/**
|
||||
* The state.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* ReportWorkerBase constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The configuration of the instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin id.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin definition.
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state service the instance should use.
|
||||
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger
|
||||
* The logger service the instance should use.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, StateInterface $state, LoggerChannelFactoryInterface $logger) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->state = $state;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('state'),
|
||||
$container->get('logger.factory')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple reporter log and display information about the queue.
|
||||
*
|
||||
* @param int $worker
|
||||
* Worker number.
|
||||
* @param object $item
|
||||
* The $item which was stored in the cron queue.
|
||||
*/
|
||||
protected function reportWork($worker, $item) {
|
||||
if ($this->state->get('cron_example_show_status_message')) {
|
||||
drupal_set_message(
|
||||
$this->t('Queue @worker worker processed item with sequence @sequence created at @time', [
|
||||
'@worker' => $worker,
|
||||
'@sequence' => $item->sequence,
|
||||
'@time' => date_iso8601($item->created),
|
||||
])
|
||||
);
|
||||
}
|
||||
$this->logger->get('cron_example')->info('Queue @worker worker processed item with sequence @sequence created at @time', [
|
||||
'@worker' => $worker,
|
||||
'@sequence' => $item->sequence,
|
||||
'@time' => date_iso8601($item->created),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\cron_example\Plugin\QueueWorker;
|
||||
|
||||
/**
|
||||
* A report worker.
|
||||
*
|
||||
* @QueueWorker(
|
||||
* id = "cron_example_queue_1",
|
||||
* title = @Translation("First worker in cron_example"),
|
||||
* cron = {"time" = 1}
|
||||
* )
|
||||
*
|
||||
* QueueWorkers are new in Drupal 8. They define a queue, which in this case
|
||||
* is identified as cron_example_queue_1 and contain a process that operates on
|
||||
* all the data given to the queue.
|
||||
*
|
||||
* @see queue_example.module
|
||||
*/
|
||||
class ReportWorkerOne extends ReportWorkerBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processItem($data) {
|
||||
$this->reportWork(1, $data);
|
||||
}
|
||||
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\cron_example\Plugin\QueueWorker;
|
||||
|
||||
/**
|
||||
* A report worker.
|
||||
*
|
||||
* @QueueWorker(
|
||||
* id = "cron_example_queue_2",
|
||||
* title = @Translation("Second worker in cron_example"),
|
||||
* cron = {"time" = 20}
|
||||
* )
|
||||
*
|
||||
* QueueWorkers are new in Drupal 8. They define a queue, which in this case
|
||||
* is identified as cron_example_queue_2 and contain a process that operates on
|
||||
* all the data given to the queue.
|
||||
*
|
||||
* @see queue_example.module
|
||||
*/
|
||||
class ReportWorkerTwo extends ReportWorkerBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processItem($data) {
|
||||
$this->reportWork(2, $data);
|
||||
}
|
||||
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\cron_example\Functional;
|
||||
|
||||
use Drupal\Tests\examples\Functional\ExamplesBrowserTestBase;
|
||||
|
||||
/**
|
||||
* Test the functionality for the Cron Example.
|
||||
*
|
||||
* @ingroup cron_example
|
||||
*
|
||||
* @group cron_example
|
||||
* @group examples
|
||||
*/
|
||||
class CronExampleTest extends ExamplesBrowserTestBase {
|
||||
|
||||
/**
|
||||
* An editable config object for access to 'cron_example.settings'.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $cronConfig;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['cron_example', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
// Create user. Search content permission granted for the search block to
|
||||
// be shown.
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer site configuration', 'access content']));
|
||||
|
||||
$this->cronConfig = \Drupal::configFactory()->getEditable('cron_example.settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an example node, test block through admin and user interfaces.
|
||||
*/
|
||||
public function testCronExampleBasic() {
|
||||
$assert = $this->assertSession();
|
||||
|
||||
// Pretend that cron has never been run (even though simpletest seems to
|
||||
// run it once...).
|
||||
\Drupal::state()->set('cron_example.next_execution', 0);
|
||||
$this->drupalGet('examples/cron-example');
|
||||
|
||||
// Initial run should cause cron_example_cron() to fire.
|
||||
$post = [];
|
||||
$this->drupalPostForm('examples/cron-example', $post, 'Run cron now');
|
||||
$assert->pageTextContains('cron_example executed at');
|
||||
|
||||
// Forcing should also cause cron_example_cron() to fire.
|
||||
$post['cron_reset'] = TRUE;
|
||||
$this->drupalPostForm(NULL, $post, 'Run cron now');
|
||||
$assert->pageTextContains('cron_example executed at');
|
||||
|
||||
// But if followed immediately and not forced, it should not fire.
|
||||
$post['cron_reset'] = FALSE;
|
||||
$this->drupalPostForm(NULL, $post, 'Run cron now');
|
||||
$assert->statusCodeEquals(200);
|
||||
$assert->pageTextNotContains('cron_example executed at');
|
||||
$assert->pageTextContains('There are currently 0 items in queue 1 and 0 items in queue 2');
|
||||
|
||||
$post = [
|
||||
'num_items' => 5,
|
||||
'queue' => 'cron_example_queue_1',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $post, 'Add jobs to queue');
|
||||
$assert->pageTextContains('There are currently 5 items in queue 1 and 0 items in queue 2');
|
||||
|
||||
$post = [
|
||||
'num_items' => 100,
|
||||
'queue' => 'cron_example_queue_2',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $post, 'Add jobs to queue');
|
||||
$assert->pageTextContains('There are currently 5 items in queue 1 and 100 items in queue 2');
|
||||
|
||||
$this->drupalPostForm('examples/cron-example', [], 'Run cron now');
|
||||
$assert->responseMatches('/Queue 1 worker processed item with sequence 5 /');
|
||||
$assert->responseMatches('/Queue 2 worker processed item with sequence 100 /');
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user