activated pathauto for url aliases
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,83 @@
|
||||
#Pathauto
|
||||
|
||||
If you are developing for this module, have a look at pathauto.api.php.
|
||||
|
||||
##Description
|
||||
|
||||
The Pathauto module provides support functions for other modules to
|
||||
automatically generate aliases based on appropriate criteria, with a
|
||||
central settings path for site administrators.
|
||||
|
||||
Implementations are provided for core entity types: content, taxonomy terms,
|
||||
and users (including blogs and forum pages).
|
||||
|
||||
Pathauto also provides a way to delete large numbers of aliases. This feature
|
||||
is available at Administer > Configuration > Search and metadata > URL aliases >
|
||||
Delete aliases.
|
||||
|
||||
##Benefits
|
||||
|
||||
Besides making the page address more reflective of its content than
|
||||
"node/138", it's important to know that modern search engines give
|
||||
heavy weight to search terms which appear in a page's URL. By
|
||||
automatically using keywords based directly on the page content in the URL,
|
||||
relevant search engine hits for your page can be significantly
|
||||
enhanced.
|
||||
|
||||
##Notices
|
||||
|
||||
Pathauto just adds URL aliases to content, users, and taxonomy terms.
|
||||
Because it's an alias, the standard Drupal URL (for example node/123 or
|
||||
taxonomy/term/1) will still function as normal. If you have external links
|
||||
to your site pointing to standard Drupal URLs, or hardcoded links in a module,
|
||||
template, content or menu which point to standard Drupal URLs it will bypass
|
||||
the alias set by Pathauto.
|
||||
|
||||
There are reasons you might not want two URLs for the same content on your
|
||||
site. If this applies to you, please note that you will need to update any
|
||||
hard coded links in your content or blocks.
|
||||
|
||||
If you use the "system path" (i.e. node/10) for menu items and settings like
|
||||
that, Drupal will replace it with the url_alias.
|
||||
|
||||
For external links, you might want to consider the Path Redirect or
|
||||
Global Redirect modules, which allow you to set forwarding either per item or
|
||||
across the site to your aliased URLs.
|
||||
|
||||
###URLs (not) Getting Replaced With Aliases:
|
||||
Please bear in mind that only URLs passed through Drupal's Drupal's URL and
|
||||
Link APIs will be replaced with their aliases during page output. If
|
||||
a module or your template contains hardcoded links, such as
|
||||
'href="node/$node->nid"', those won't get replaced with their corresponding
|
||||
aliases.
|
||||
|
||||
## Disabling Pathauto for a specific content type (or taxonomy)
|
||||
|
||||
When the pattern for a content type is left blank, the default pattern will be
|
||||
used. But if the default pattern is also blank, Pathauto will be disabled
|
||||
for that content type.
|
||||
|
||||
## Installing Pathauto
|
||||
1. Install the module as normal, note that there are two dependencies.
|
||||
2. Configure the module at admin/config/search/path/patterns - add a new pattern by creating by clicking "Add Pathauto pattern".
|
||||
3. Fill out "Path pattern" with fx [node:title], choose which content types this applies to, give it a label (the name) and save it.
|
||||
4. When you save new content from now on, it should automatically be assigned an alternative URL
|
||||
|
||||
##Credits:
|
||||
|
||||
The original module combined the functionality of Mike Ryan's autopath with
|
||||
Tommy Sundstrom's path_automatic.
|
||||
|
||||
Significant enhancements were contributed by jdmquin @ www.bcdems.net.
|
||||
|
||||
Matt England added the tracker support (tracker support has been removed in
|
||||
recent changes).
|
||||
|
||||
Other suggestions and patches contributed by the Drupal community.
|
||||
|
||||
Current maintainers:
|
||||
|
||||
- Dave Reid - http://www.davereid.net
|
||||
- Greg Knaddison - http://www.knaddison.com
|
||||
- Mike Ryan - http://mikeryan.name
|
||||
- Frederik 'Freso' S. Olesen - http://freso.dk
|
||||
@@ -0,0 +1,13 @@
|
||||
enabled_entity_types:
|
||||
- user
|
||||
punctuation:
|
||||
hyphen: 1
|
||||
verbose : FALSE
|
||||
separator : '-'
|
||||
max_length : 100
|
||||
max_component_length: 100
|
||||
transliterate : TRUE
|
||||
reduce_ascii : FALSE
|
||||
case : TRUE
|
||||
ignore_words : 'a, an, as, at, before, but, by, for, from, is, in, into, like, of, off, on, onto, per, since, than, the, this, that, to, up, via, with'
|
||||
update_action : 2
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
id: pathauto_update_alias_node
|
||||
label: 'Update URL alias'
|
||||
status: true
|
||||
langcode: en
|
||||
type: node
|
||||
plugin: pathauto_update_alias
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
id: pathauto_update_alias_user
|
||||
label: 'Update URL alias'
|
||||
status: true
|
||||
langcode: en
|
||||
type: user
|
||||
plugin: pathauto_update_alias
|
||||
dependencies:
|
||||
module:
|
||||
- user
|
||||
@@ -0,0 +1,36 @@
|
||||
pathauto.settings:
|
||||
type: config_object
|
||||
mapping:
|
||||
enabled_entity_types:
|
||||
label: Enabled entity types
|
||||
type: sequence
|
||||
sequence:
|
||||
type: string
|
||||
punctuation:
|
||||
type: sequence
|
||||
sequence:
|
||||
type: integer
|
||||
verbose:
|
||||
type: boolean
|
||||
separator:
|
||||
type: string
|
||||
max_length:
|
||||
type: integer
|
||||
max_component_length:
|
||||
type: integer
|
||||
transliterate:
|
||||
type: boolean
|
||||
reduce_ascii:
|
||||
type: boolean
|
||||
ignore_words:
|
||||
type: string
|
||||
case:
|
||||
type: boolean
|
||||
ignore_words:
|
||||
type: string
|
||||
update_action:
|
||||
type: integer
|
||||
|
||||
action.configuration.pathauto_update_alias:
|
||||
type: action_configuration_default
|
||||
label: 'Update URL alias'
|
||||
@@ -0,0 +1,40 @@
|
||||
pathauto.pattern.*:
|
||||
type: config_entity
|
||||
label: 'Pathauto pattern config'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'ID'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
uuid:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
label: 'Pattern type'
|
||||
pattern:
|
||||
type: string
|
||||
label: 'Pattern'
|
||||
selection_criteria:
|
||||
type: sequence
|
||||
label: 'Selection criteria'
|
||||
sequence:
|
||||
type: condition.plugin.[id]
|
||||
label: 'Selection condition'
|
||||
selection_logic:
|
||||
type: string
|
||||
label: 'Selection logic'
|
||||
weight:
|
||||
type: integer
|
||||
label: 'Weight'
|
||||
relationships:
|
||||
type: sequence
|
||||
label: 'Context definitions'
|
||||
sequence:
|
||||
- type: mapping
|
||||
label: 'Relationship'
|
||||
mapping:
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Documentation for pathauto API.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Language\Language;
|
||||
|
||||
/**
|
||||
* @todo Update for 8.x-1.x
|
||||
*
|
||||
* It may be helpful to review some examples of integration from
|
||||
* pathauto.pathauto.inc.
|
||||
*
|
||||
* Pathauto works by using tokens in path patterns. Thus the simplest
|
||||
* integration is just to provide tokens. Token support is provided by Drupal
|
||||
* core. To provide additional token from your module, implement the following
|
||||
* hooks:
|
||||
*
|
||||
* hook_tokens() - http://api.drupal.org/api/function/hook_tokens
|
||||
* hook_token_info() - http://api.drupal.org/api/function/hook_token_info
|
||||
*
|
||||
* If you wish to provide pathauto integration for custom paths provided by your
|
||||
* module, there are a few steps involved.
|
||||
*
|
||||
* 1. hook_pathauto()
|
||||
* Provide information required by pathauto for the settings form as well as
|
||||
* bulk generation. See the documentation for hook_pathauto() for more
|
||||
* details.
|
||||
*
|
||||
* 2. pathauto_create_alias()
|
||||
* At the appropriate time (usually when a new item is being created for
|
||||
* which a generated alias is desired), call pathauto_create_alias() with the
|
||||
* appropriate parameters to generate and create the alias. See the user,
|
||||
* taxonomy, and node hook implementations in pathauto.module for examples.
|
||||
* Also see the documentation for pathauto_create_alias().
|
||||
*
|
||||
* 3. pathauto_path_delete_all()
|
||||
* At the appropriate time (usually when an item is being deleted), call
|
||||
* pathauto_path_delete_all() to remove any aliases that were created for the
|
||||
* content being removed. See the documentation for
|
||||
* pathauto_path_delete_all() for more details.
|
||||
*
|
||||
* 4. hook_path_alias_types()
|
||||
* For modules that create new types of content that can be aliased with
|
||||
* pathauto, a hook implementation is needed to allow the user to delete them
|
||||
* all at once. See the documentation for hook_path_alias_types() below for
|
||||
* more information.
|
||||
*
|
||||
* There are other integration points with pathauto, namely alter hooks that
|
||||
* allow you to change the data used by pathauto at various points in the
|
||||
* process. See the below hook documentation for details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Alter pathauto alias type definitions.
|
||||
*
|
||||
* @param array &$definitions
|
||||
* Alias type definitions.
|
||||
*/
|
||||
function hook_path_alias_types_alter(array &$definitions) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a possible URL alias would conflict with any existing paths.
|
||||
*
|
||||
* Returning TRUE from this function will trigger pathauto_alias_uniquify() to
|
||||
* generate a similar URL alias with a suffix to avoid conflicts.
|
||||
*
|
||||
* @param string $alias
|
||||
* The potential URL alias.
|
||||
* @param string $source
|
||||
* The source path for the alias (e.g. 'node/1').
|
||||
* @param string $langcode
|
||||
* The language code for the alias (e.g. 'en').
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if $alias conflicts with an existing, reserved path, or FALSE/NULL if
|
||||
* it does not match any reserved paths.
|
||||
*
|
||||
* @see pathauto_alias_uniquify()
|
||||
*/
|
||||
function hook_pathauto_is_alias_reserved($alias, $source, $langcode) {
|
||||
// Check our module's list of paths and return TRUE if $alias matches any of
|
||||
// them.
|
||||
return (bool) \Drupal::database()->query("SELECT 1 FROM {mytable} WHERE path = :path", [':path' => $alias])->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the pattern to be used before an alias is generated by Pathauto.
|
||||
*
|
||||
* This hook will only be called if a default pattern is configured (on
|
||||
* admin/config/search/path/patterns).
|
||||
*
|
||||
* @param string $pattern
|
||||
* The alias pattern for Pathauto to pass to token_replace() to generate the
|
||||
* URL alias.
|
||||
* @param array $context
|
||||
* An associative array of additional options, with the following elements:
|
||||
* - 'module': The module or entity type being aliased.
|
||||
* - 'op': A string with the operation being performed on the object being
|
||||
* aliased. Can be either 'insert', 'update', 'return', or 'bulkupdate'.
|
||||
* - 'source': A string of the source path for the alias (e.g. 'node/1').
|
||||
* - 'data': An array of keyed objects to pass to token_replace().
|
||||
* - 'type': The sub-type or bundle of the object being aliased.
|
||||
* - 'language': A string of the language code for the alias (e.g. 'en').
|
||||
* This can be altered by reference.
|
||||
*/
|
||||
function hook_pathauto_pattern_alter(&$pattern, array $context) {
|
||||
// Switch out any [node:created:*] tokens with [node:updated:*] on update.
|
||||
if ($context['module'] == 'node' && ($context['op'] == 'update')) {
|
||||
$pattern = preg_replace('/\[node:created(\:[^]]*)?\]/', '[node:updated$1]', $pattern);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter Pathauto-generated aliases before saving.
|
||||
*
|
||||
* @param string $alias
|
||||
* The automatic alias after token replacement and strings cleaned.
|
||||
* @param array $context
|
||||
* An associative array of additional options, with the following elements:
|
||||
* - 'module': The module or entity type being aliased.
|
||||
* - 'op': A string with the operation being performed on the object being
|
||||
* aliased. Can be either 'insert', 'update', 'return', or 'bulkupdate'.
|
||||
* - 'source': A string of the source path for the alias (e.g. 'node/1').
|
||||
* This can be altered by reference.
|
||||
* - 'data': An array of keyed objects to pass to token_replace().
|
||||
* - 'type': The sub-type or bundle of the object being aliased.
|
||||
* - 'language': A string of the language code for the alias (e.g. 'en').
|
||||
* This can be altered by reference.
|
||||
* - 'pattern': A string of the pattern used for aliasing the object.
|
||||
*/
|
||||
function hook_pathauto_alias_alter(&$alias, array &$context) {
|
||||
// Add a suffix so that all aliases get saved as 'content/my-title.html'
|
||||
$alias .= '.html';
|
||||
|
||||
// Force all aliases to be saved as language neutral.
|
||||
$context['language'] = Language::LANGCODE_NOT_SPECIFIED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the list of punctuation characters for Pathauto control.
|
||||
*
|
||||
* @param $punctuation
|
||||
* An array of punctuation to be controlled by Pathauto during replacement
|
||||
* keyed by punctuation name. Each punctuation record should be an array
|
||||
* with the following key/value pairs:
|
||||
* - value: The raw value of the punctuation mark.
|
||||
* - name: The human-readable name of the punctuation mark. This must be
|
||||
* translated using t() already.
|
||||
*/
|
||||
function hook_pathauto_punctuation_chars_alter(array &$punctuation) {
|
||||
// Add the trademark symbol.
|
||||
$punctuation['trademark'] = array('value' => '™', 'name' => t('Trademark symbol'));
|
||||
|
||||
// Remove the dollar sign.
|
||||
unset($punctuation['dollar']);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
name : 'Pathauto'
|
||||
description : 'Provides a mechanism for modules to automatically generate aliases for the content they manage.'
|
||||
# core: 8.x
|
||||
type: module
|
||||
|
||||
dependencies:
|
||||
- ctools:ctools
|
||||
- drupal:path
|
||||
- token:token
|
||||
|
||||
configure: entity.pathauto_pattern.collection
|
||||
|
||||
recommends:
|
||||
- redirect:redirect
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-03-03
|
||||
version: '8.x-1.1'
|
||||
core: '8.x'
|
||||
project: 'pathauto'
|
||||
datestamp: 1520092688
|
||||
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update, and uninstall functions for Pathauto.
|
||||
*
|
||||
* @ingroup pathauto
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Plugin\Context\Context;
|
||||
use Drupal\Core\Plugin\Context\ContextDefinition;
|
||||
use Drupal\pathauto\Entity\PathautoPattern;
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function pathauto_install() {
|
||||
// Set the weight to 1
|
||||
module_set_weight('pathauto', 1);
|
||||
|
||||
// Ensure the url_alias table exists.
|
||||
_pathauto_ensure_url_alias_table_exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to ensure the url_alias table exists.
|
||||
*
|
||||
* Only necessary on Drupal 8.1.x.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2704821
|
||||
*/
|
||||
function _pathauto_ensure_url_alias_table_exists() {
|
||||
$alias_storage = \Drupal::service('path.alias_storage');
|
||||
if (method_exists($alias_storage, 'schemaDefinition')) {
|
||||
$database_schema = \Drupal::database()->schema();
|
||||
if (!$database_schema->tableExists($alias_storage::TABLE)) {
|
||||
$schema_definition = $alias_storage->schemaDefinition();
|
||||
$database_schema->createTable($alias_storage::TABLE, $schema_definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates pathauto widgets to use the path widget ID.
|
||||
*/
|
||||
function pathauto_update_8001() {
|
||||
|
||||
// Replace values in the 'entity.definitions.installed' keyvalue collection.
|
||||
$collection = \Drupal::service('keyvalue')->get('entity.definitions.installed');
|
||||
foreach ($collection->getAll() as $key => $definitions) {
|
||||
if (!is_array($definitions) || empty($definitions['path'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Retrieve and change path base field definition.
|
||||
$path_definition = $definitions['path'];
|
||||
if (($options = $path_definition->getDisplayOptions('form')) && $options['type'] = 'pathauto') {
|
||||
$options['type'] = 'path';
|
||||
$path_definition->setDisplayOptions('form', $options);
|
||||
// Save the new value.
|
||||
$collection->set($key, $definitions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach (EntityFormDisplay::loadMultiple() as $form_display) {
|
||||
if ($component = $form_display->getComponent('path')) {
|
||||
if (isset($component['type']) && $component['type'] == 'pathauto') {
|
||||
$component['type'] = 'path';
|
||||
$form_display->setComponent('path', $component);
|
||||
$form_display->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts patterns from configuration objects to configuration entities.
|
||||
*/
|
||||
function pathauto_update_8100() {
|
||||
\Drupal::service('module_installer')->install(['ctools']);
|
||||
|
||||
$messages = array();
|
||||
/** @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_bundle_info */
|
||||
$entity_bundle_info = \Drupal::service('entity_type.bundle.info');
|
||||
$entity_type_manager = \Drupal::entityTypeManager();
|
||||
$language_manager = \Drupal::languageManager();
|
||||
$entity_type_manager->clearCachedDefinitions();
|
||||
\Drupal::service('plugin.manager.alias_type')->clearCachedDefinitions();
|
||||
$entity_types = $entity_type_manager->getDefinitions();
|
||||
|
||||
// 1. Load all patterns.
|
||||
$config = \Drupal::configFactory()->getEditable('pathauto.pattern');
|
||||
$patterns = $config->get('patterns');
|
||||
|
||||
// 2. Create a configuration entity per pattern.
|
||||
foreach ($patterns as $entity_type => $entity_patterns) {
|
||||
if (!array_key_exists($entity_type, $entity_types)) {
|
||||
// We found an unknown entity type. Report it.
|
||||
$messages[] = t('Entity of type @type was not processed. It defines the following patterns: @patterns', array(
|
||||
'@type' => $entity_type,
|
||||
'@patterns' => print_r($entity_patterns, TRUE),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
$entity_label = $entity_types[$entity_type]->getLabel();
|
||||
|
||||
if (!empty($entity_patterns['default'])) {
|
||||
// This is a pattern for an entity type, such as "node".
|
||||
$pattern = PathautoPattern::create([
|
||||
'id' => $entity_type,
|
||||
'label' => $entity_label,
|
||||
'type' => 'canonical_entities:' . $entity_type,
|
||||
'pattern' => $entity_patterns['default'],
|
||||
'weight' => 0,
|
||||
]);
|
||||
$pattern->save();
|
||||
}
|
||||
|
||||
// Loop over bundles and create patterns if they have a value.
|
||||
// Bundle keys may have a language suffix for language-dependant patterns.
|
||||
if (isset($entity_patterns['bundles'])) {
|
||||
$bundle_info = $entity_bundle_info->getBundleInfo($entity_type);
|
||||
foreach ($entity_patterns['bundles'] as $bundle => $bundle_patterns) {
|
||||
if (empty($bundle_patterns['default'])) {
|
||||
// This bundle does not define a pattern. Move on to the next one.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($bundle_info[$bundle])) {
|
||||
// This is a pattern for a bundle, such as "node_article".
|
||||
$pattern = PathautoPattern::create([
|
||||
'id' => $entity_type . '_' . $bundle,
|
||||
'label' => $entity_label . ' ' . $bundle_info[$bundle]['label'],
|
||||
'type' => 'canonical_entities:' . $entity_type,
|
||||
'pattern' => $bundle_patterns['default'],
|
||||
'weight' => -5,
|
||||
]);
|
||||
|
||||
// Add the bundle condition.
|
||||
$pattern->addSelectionCondition([
|
||||
'id' => 'entity_bundle:' . $entity_type,
|
||||
'bundles' => array($bundle => $bundle),
|
||||
'negate' => FALSE,
|
||||
'context_mapping' => [ $entity_type => $entity_type ],
|
||||
]);
|
||||
|
||||
$pattern->save();
|
||||
}
|
||||
else {
|
||||
// This is either a language dependent pattern such as "article_es" or
|
||||
// an unknown bundle or langcode. Let's figure it out.
|
||||
$matches = NULL;
|
||||
$langcode = NULL;
|
||||
$extracted_bundle = NULL;
|
||||
$language = NULL;
|
||||
preg_match('/^(.*)_([a-z-]*)$/', $bundle, $matches);
|
||||
if (count($matches) == 3) {
|
||||
list(, $extracted_bundle, $langcode) = $matches;
|
||||
$language = $language_manager->getLanguage($langcode);
|
||||
}
|
||||
// Validate bundle, langcode and language.
|
||||
if (!isset($bundle_info[$extracted_bundle]) || ($langcode == NULL) || ($language == NULL)) {
|
||||
$messages[] = t('Unrecognized entity bundle @entity:@bundle was not processed. It defines the following patterns: @patterns', array(
|
||||
'@entity' => $entity_type,
|
||||
'@bundle' => $bundle,
|
||||
'@patterns' => print_r($entity_patterns, TRUE),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is a pattern for a bundle and a language, such as "node_article_es".
|
||||
$pattern = PathautoPattern::create([
|
||||
'id' => $entity_type . '_' . $extracted_bundle . '_' . str_replace('-', '_', $langcode),
|
||||
'label' => $entity_label . ' ' . $bundle_info[$extracted_bundle]['label'] . ' ' . $language->getName(),
|
||||
'type' => 'canonical_entities:' . $entity_type,
|
||||
'pattern' => $bundle_patterns['default'],
|
||||
'weight' => -10,
|
||||
]);
|
||||
|
||||
// Add the bundle condition.
|
||||
$pattern->addSelectionCondition([
|
||||
'id' => 'entity_bundle:' . $entity_type,
|
||||
'bundles' => array($extracted_bundle => $extracted_bundle),
|
||||
'negate' => FALSE,
|
||||
'context_mapping' => [ $entity_type => $entity_type ],
|
||||
]);
|
||||
|
||||
// Add the language condition.
|
||||
$language_mapping = $entity_type . ':' . $entity_type_manager->getDefinition($entity_type)->getKey('langcode') . ':language';
|
||||
$pattern->addSelectionCondition([
|
||||
'id' => 'language',
|
||||
'langcodes' => [ $langcode => $langcode ],
|
||||
'negate' => FALSE,
|
||||
'context_mapping' => [
|
||||
'language' => $language_mapping,
|
||||
]
|
||||
]);
|
||||
|
||||
// Add the context relationship for this language.
|
||||
$pattern->addRelationship($language_mapping, 'Language');
|
||||
|
||||
$pattern->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Delete the old configuration object that stores patterns.
|
||||
$config->delete();
|
||||
|
||||
// 4. Print out messages.
|
||||
if (!empty($messages)) {
|
||||
return implode('</br>', $messages);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update relationship storage.
|
||||
*/
|
||||
function pathauto_update_8101() {
|
||||
foreach (\Drupal::configFactory()->listAll('pathauto.pattern.') as $pattern_config_name) {
|
||||
$pattern_config = \Drupal::configFactory()->getEditable($pattern_config_name);
|
||||
|
||||
$relationships = [];
|
||||
foreach ((array) $pattern_config->get('context_definitions') as $context_definition) {
|
||||
$relationships[$context_definition['id']] = ['label' => $context_definition['label']];
|
||||
}
|
||||
|
||||
$pattern_config->clear('context_definitions');
|
||||
$pattern_config->set('relationships', $relationships);
|
||||
$pattern_config->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update node type conditions from entity_bundle to node_type.
|
||||
*/
|
||||
function pathauto_update_8102() {
|
||||
// Load all pattern configuration entities.
|
||||
foreach (\Drupal::configFactory()->listAll('pathauto.pattern.') as $pattern_config_name) {
|
||||
$pattern_config = \Drupal::configFactory()->getEditable($pattern_config_name);
|
||||
|
||||
// Loop patterns and swap the entity_bundle:node plugin by the node_type
|
||||
// plugin.
|
||||
if ($pattern_config->get('type') == 'canonical_entities:node') {
|
||||
$selection_criteria = $pattern_config->get('selection_criteria');
|
||||
foreach ($selection_criteria as $uuid => $condition) {
|
||||
if ($condition['id'] == 'entity_bundle:node') {
|
||||
$selection_criteria[$uuid]['id'] = 'node_type';
|
||||
$pattern_config->set('selection_criteria', $selection_criteria);
|
||||
$pattern_config->save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix invalid default value for ignore_words.
|
||||
*/
|
||||
function pathauto_update_8103() {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
$config = $config_factory->getEditable('pathauto.settings');
|
||||
$ignore_words = $config->get('ignore_words');
|
||||
if ($ignore_words === ', in, is,that, the , this, with, ') {
|
||||
$config->set('ignore_words', 'a, an, as, at, before, but, by, for, from, is, in, into, like, of, off, on, onto, per, since, than, the, this, that, to, up, via, with')->save(TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resave patterns so that lookup keys are updated.
|
||||
*/
|
||||
function pathauto_update_8104() {
|
||||
\Drupal::entityTypeManager()->clearCachedDefinitions();
|
||||
// Load all pattern configuration entities and save them, so that the new
|
||||
// status lookup keys are saved.
|
||||
foreach (\Drupal::configFactory()->listAll('pathauto.pattern.') as $pattern_config_name) {
|
||||
$pattern_config = \Drupal::configFactory()->getEditable($pattern_config_name);
|
||||
$pattern_config->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the url_alias table exists.
|
||||
*/
|
||||
function pathauto_update_8105() {
|
||||
_pathauto_ensure_url_alias_table_exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update default configuration for enabled entity types.
|
||||
*/
|
||||
function pathauto_update_8106() {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
$config = $config_factory->getEditable('pathauto.settings');
|
||||
$config->set('enabled_entity_types', ['user']);
|
||||
$config->save();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
(function ($) {
|
||||
'use strict';
|
||||
Drupal.behaviors.pathFieldsetSummaries = {
|
||||
attach: function (context) {
|
||||
$('fieldset.path-form', context).drupalSetSummary(function (context) {
|
||||
var path = $('.form-item-path-alias input', context).val();
|
||||
var automatic = $('.form-item-path-pathauto input', context).attr('checked');
|
||||
|
||||
if (automatic) {
|
||||
return Drupal.t('Automatic alias');
|
||||
}
|
||||
else if (path) {
|
||||
return Drupal.t('Alias: @alias', {'@alias': path});
|
||||
}
|
||||
else {
|
||||
return Drupal.t('No alias');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,9 @@
|
||||
widget:
|
||||
version: 1.0
|
||||
js:
|
||||
pathauto.js: {}
|
||||
dependencies:
|
||||
- core/jquery
|
||||
- core/drupal
|
||||
- core/drupalSettings
|
||||
- core/drupal.form
|
||||
@@ -0,0 +1,6 @@
|
||||
entity.pathauto_pattern.add_form:
|
||||
route_name: 'entity.pathauto_pattern.add_form'
|
||||
title: 'Add Pathauto pattern'
|
||||
appears_on:
|
||||
- entity.pathauto_pattern.collection
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
pathauto.patterns.form:
|
||||
route_name: entity.pathauto_pattern.collection
|
||||
base_route: path.admin_overview
|
||||
title: 'Patterns'
|
||||
weight: 10
|
||||
|
||||
pathauto.settings.form:
|
||||
route_name: pathauto.settings.form
|
||||
base_route: path.admin_overview
|
||||
title: 'Settings'
|
||||
weight: 20
|
||||
|
||||
pathauto.bulk.update.form:
|
||||
route_name: pathauto.bulk.update.form
|
||||
base_route: path.admin_overview
|
||||
title: 'Bulk generate'
|
||||
weight: 30
|
||||
|
||||
pathauto.admin.delete:
|
||||
route_name: pathauto.admin.delete
|
||||
base_route: path.admin_overview
|
||||
title: 'Delete aliases'
|
||||
weight: 40
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @defgroup pathauto Pathauto: Automatically generates aliases for content
|
||||
*
|
||||
* The Pathauto module automatically generates path aliases for various kinds of
|
||||
* content (nodes, categories, users) without requiring the user to manually
|
||||
* specify the path alias. This allows you to get aliases like
|
||||
* /category/my-node-title.html instead of /node/123. The aliases are based upon
|
||||
* a "pattern" system which the administrator can control.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Main file for the Pathauto module, which automatically generates aliases for content.
|
||||
*
|
||||
* @ingroup pathauto
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\pathauto\PathautoFieldItemList;
|
||||
use Drupal\pathauto\PathautoItem;
|
||||
|
||||
/**
|
||||
* The default ignore word list.
|
||||
*/
|
||||
define('PATHAUTO_IGNORE_WORDS', 'a, an, as, at, before, but, by, for, from, is, in, into, like, of, off, on, onto, per, since, than, the, this, that, to, up, via, with');
|
||||
|
||||
/**
|
||||
* Implements hook_hook_info().
|
||||
*/
|
||||
function pathauto_hook_info() {
|
||||
$hooks = array(
|
||||
'pathauto_pattern_alter',
|
||||
'pathauto_alias_alter',
|
||||
'pathauto_is_alias_reserved',
|
||||
);
|
||||
return array_fill_keys($hooks, array('group' => 'pathauto'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function pathauto_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.pathauto':
|
||||
$output = '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Pathauto module provides a mechanism to automate the creation of <a href="path">path</a> aliases. This makes URLs more readable and helps search engines index content more effectively. For more information, see the <a href=":online">online documentation for Pathauto</a>.', [':online' => 'https://www.drupal.org/documentation/modules/pathauto']) . '</p>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dd>' . t('Pathauto is accessed from the tabs it adds to the list of <a href=":aliases">URL aliases</a>.', [':aliases' => Url::fromRoute('path.admin_overview')->toString()]) . '</dd>';
|
||||
$output .= '<dt>' . t('Creating Pathauto Patterns') . '</dt>';
|
||||
$output .= '<dd>' . t('The <a href=":pathauto_pattern">"Patterns"</a> page is used to configure automatic path aliasing. New patterns are created here using the <a href=":add_form">Add Pathauto pattern</a> button which presents a form to simplify pattern creation thru the use of <a href="token">available tokens</a>. The patterns page provides a list of all patterns on the site and allows you to edit and reorder them. An alias is generated for the first pattern that applies.', [':pathauto_pattern' => Url::fromRoute('entity.pathauto_pattern.collection')->toString(), ':add_form' => Url::fromRoute('entity.pathauto_pattern.add_form')->toString()]) . '</dd>';
|
||||
$output .= '<dt>' . t('Pathauto Settings') . '</dt>';
|
||||
$output .= '<dd>' . t('The <a href=":settings">"Settings"</a> page is used to customize global Pathauto settings for automated pattern creation.', [':settings' => Url::fromRoute('pathauto.settings.form')->toString()]) . '</dd>';
|
||||
$output .= '<dd>' . t('The <strong>maximum alias length</strong> and <strong>maximum component length</strong> values default to 100 and have a limit of @max from Pathauto. You should enter a value that is the length of the "alias" column of the url_alias database table minus the length of any strings that might get added to the end of the URL. The recommended and default value is 100.', array('@max' => \Drupal::service('pathauto.alias_storage_helper')->getAliasSchemaMaxlength())) . '</dd>';
|
||||
$output .= '<dt>' . t('Bulk Generation') . '</dt>';
|
||||
$output .= '<dd>' . t('The <a href=":pathauto_bulk">"Bulk Generate"</a> page allows you to create URL aliases for items that currently have no aliases. This is typically used when installing Pathauto on a site that has existing un-aliased content that needs to be aliased in bulk.', [':pathauto_bulk' => Url::fromRoute('pathauto.bulk.update.form')->toString()]) . '</dd>';
|
||||
$output .= '<dt>' . t('Delete Aliases') . '</dt>';
|
||||
$output .= '<dd>' . t('The <a href=":pathauto_delete">"Delete Aliases"</a> page allows you to remove URL aliases from items that have previously been assigned aliases using pathauto.', [':pathauto_delete' => Url::fromRoute('pathauto.admin.delete')->toString()]) . '</dd>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
|
||||
case 'entity.pathauto_pattern.collection':
|
||||
$output = '<p>' . t('This page provides a list of all patterns on the site and allows you to edit and reorder them.') . '</p>';
|
||||
return $output;
|
||||
|
||||
case 'entity.pathauto_pattern.add_form':
|
||||
$output = '<p>' . t('You need to select a pattern type, then a pattern and filter, and a label. Additional types can be enabled on the <a href=":settings">Settings</a> page.', [':settings' => Url::fromRoute('pathauto.settings.form')->toString()]) . '</p>';
|
||||
return $output;
|
||||
|
||||
case 'pathauto.bulk.update.form':
|
||||
$output = '<p>' . t('Bulk generation can be used to generate URL aliases for items that currently have no aliases. This is typically used when installing Pathauto on a site that has existing un-aliased content that needs to be aliased in bulk.') . '<br>';
|
||||
$output .= t('It can also be used to regenerate URL aliases for items that have an old alias and for which the Pathauto pattern has been changed.') . '</p>';
|
||||
$output .= '<p>' . t('Note that this will only affect items which are configured to have their URL alias automatically set. Items whose URL alias is manually set are not affected.') . '</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_info_alter().
|
||||
*/
|
||||
function pathauto_field_info_alter(&$info) {
|
||||
$info['path']['class'] = PathautoItem::class;
|
||||
$info['path']['list_class'] = PathautoFieldItemList::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_info_alter().
|
||||
*/
|
||||
function pathauto_field_widget_info_alter(&$widgets) {
|
||||
$widgets['path']['class'] = 'Drupal\pathauto\PathautoWidget';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_base_field_info().
|
||||
*/
|
||||
function pathauto_entity_base_field_info(EntityTypeInterface $entity_type) {
|
||||
$config = \Drupal::config('pathauto.settings');
|
||||
// Verify that the configuration data isn't null (as is the case before the
|
||||
// module's initialization, in tests), so that in_array() won't fail.
|
||||
if ($enabled_entity_types = $config->get('enabled_entity_types')) {
|
||||
if (in_array($entity_type->id(), $enabled_entity_types)) {
|
||||
$fields['path'] = BaseFieldDefinition::create('path')
|
||||
->setCustomStorage(TRUE)
|
||||
->setLabel(t('URL alias'))
|
||||
->setTranslatable(TRUE)
|
||||
->setComputed(TRUE)
|
||||
->setDisplayOptions('form', array(
|
||||
'type' => 'path',
|
||||
'weight' => 30,
|
||||
))
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the pattern field, to ensure it doesn't contain any characters that
|
||||
* are invalid in URLs.
|
||||
*/
|
||||
function pathauto_pattern_validate($element, FormStateInterface $form_state) {
|
||||
|
||||
if (isset($element['#value'])) {
|
||||
$title = empty($element['#title']) ? $element['#parents'][0] : $element['#title'];
|
||||
$invalid_characters = ['#', '?', '&'];
|
||||
$invalid_characters_used = [];
|
||||
|
||||
foreach ($invalid_characters as $invalid_character) {
|
||||
if (strpos($element['#value'], $invalid_character) !== FALSE) {
|
||||
$invalid_characters_used[] = $invalid_character;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($invalid_characters_used)) {
|
||||
$form_state->setError($element, t('The %element-title is using the following invalid characters: @invalid-characters.', array('%element-title' => $title, '@invalid-characters' => implode(', ', $invalid_characters_used))));
|
||||
}
|
||||
|
||||
if (preg_match('/(\s$)+/', $element['#value'])) {
|
||||
$form_state->setError($element, t('The %element-title doesn\'t allow the patterns ending with whitespace.', array('%element-title' => $title)));
|
||||
}
|
||||
}
|
||||
|
||||
return $element;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
administer pathauto:
|
||||
title: 'Administer pathauto'
|
||||
description: 'Allows a user to configure patterns for automated aliases and bulk delete URL-aliases.'
|
||||
notify of path changes:
|
||||
title: 'Notify of Path Changes'
|
||||
description: 'Determines whether or not users are notified.'
|
||||
@@ -0,0 +1,54 @@
|
||||
entity.pathauto_pattern.collection:
|
||||
path: '/admin/config/search/path/patterns'
|
||||
defaults:
|
||||
_entity_list: 'pathauto_pattern'
|
||||
_title: 'Patterns'
|
||||
requirements:
|
||||
_permission: 'administer pathauto'
|
||||
|
||||
entity.pathauto_pattern.add_form:
|
||||
path: '/admin/config/search/path/patterns/add'
|
||||
defaults:
|
||||
_entity_form: 'pathauto_pattern.default'
|
||||
_title: 'Add Pathauto pattern'
|
||||
tempstore_id: 'pathauto.pattern'
|
||||
requirements:
|
||||
_permission: 'administer pathauto'
|
||||
|
||||
pathauto.settings.form:
|
||||
path: '/admin/config/search/path/settings'
|
||||
defaults:
|
||||
_form: '\Drupal\pathauto\Form\PathautoSettingsForm'
|
||||
_title: 'Settings'
|
||||
requirements:
|
||||
_permission: 'administer pathauto'
|
||||
|
||||
entity.pathauto_pattern.enable:
|
||||
path: '/admin/config/search/path/patterns/{pathauto_pattern}/enable'
|
||||
defaults:
|
||||
_entity_form: 'pathauto_pattern.enable'
|
||||
requirements:
|
||||
_entity_access: 'pathauto_pattern.update'
|
||||
|
||||
entity.pathauto_pattern.disable:
|
||||
path: '/admin/config/search/path/patterns/{pathauto_pattern}/disable'
|
||||
defaults:
|
||||
_entity_form: 'pathauto_pattern.disable'
|
||||
requirements:
|
||||
_entity_access: 'pathauto_pattern.update'
|
||||
|
||||
pathauto.bulk.update.form:
|
||||
path: '/admin/config/search/path/update_bulk'
|
||||
defaults:
|
||||
_form: '\Drupal\pathauto\Form\PathautoBulkUpdateForm'
|
||||
_title: 'Bulk generate'
|
||||
requirements:
|
||||
_permission: 'administer url aliases'
|
||||
|
||||
pathauto.admin.delete:
|
||||
path: '/admin/config/search/path/delete_bulk'
|
||||
defaults:
|
||||
_form: '\Drupal\pathauto\Form\PathautoAdminDelete'
|
||||
_title: 'Delete aliases'
|
||||
requirements:
|
||||
_permission: 'administer url aliases'
|
||||
@@ -0,0 +1,26 @@
|
||||
services:
|
||||
pathauto.generator:
|
||||
class: Drupal\pathauto\PathautoGenerator
|
||||
arguments: ['@config.factory', '@module_handler', '@token', '@pathauto.alias_cleaner', '@pathauto.alias_storage_helper', '@pathauto.alias_uniquifier', '@pathauto.verbose_messenger', '@string_translation', '@token.entity_mapper', '@entity_type.manager']
|
||||
pathauto.alias_cleaner:
|
||||
class: Drupal\pathauto\AliasCleaner
|
||||
arguments: ['@config.factory', '@pathauto.alias_storage_helper', '@language_manager', '@cache.discovery', '@transliteration', '@module_handler']
|
||||
pathauto.alias_storage_helper:
|
||||
class: Drupal\pathauto\AliasStorageHelper
|
||||
arguments: ['@config.factory', '@path.alias_storage', '@database','@pathauto.verbose_messenger', '@string_translation']
|
||||
tags:
|
||||
- { name: backend_overridable }
|
||||
pathauto.alias_uniquifier:
|
||||
class: Drupal\pathauto\AliasUniquifier
|
||||
arguments: ['@config.factory', '@pathauto.alias_storage_helper','@module_handler', '@router.route_provider', '@path.alias_manager']
|
||||
pathauto.verbose_messenger:
|
||||
class: Drupal\pathauto\VerboseMessenger
|
||||
arguments: ['@config.factory', '@current_user']
|
||||
plugin.manager.alias_type:
|
||||
class: Drupal\pathauto\AliasTypeManager
|
||||
parent: default_plugin_manager
|
||||
pathauto.settings_cache_tag:
|
||||
class: Drupal\pathauto\EventSubscriber\PathautoSettingsCacheTag
|
||||
arguments: ['@entity_field.manager', '@plugin.manager.alias_type']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Token integration for the Pathauto module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
|
||||
/**
|
||||
* Implements hook_token_info().
|
||||
*/
|
||||
function pathauto_token_info() {
|
||||
$info = array();
|
||||
|
||||
$info['tokens']['array']['join-path'] = array(
|
||||
'name' => t('Joined path'),
|
||||
'description' => t('The array values each cleaned by Pathauto and then joined with the slash into a string that resembles an URL.'),
|
||||
);
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_tokens().
|
||||
*/
|
||||
function pathauto_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
|
||||
$replacements = array();
|
||||
|
||||
if ($type == 'array' && !empty($data['array'])) {
|
||||
$array = $data['array'];
|
||||
|
||||
foreach ($tokens as $name => $original) {
|
||||
switch ($name) {
|
||||
case 'join-path':
|
||||
$values = array();
|
||||
foreach (token_element_children($array) as $key) {
|
||||
$value = is_array($array[$key]) ? render($array[$key]) : (string) $array[$key];
|
||||
$value = \Drupal::service('pathauto.alias_cleaner')->cleanString($value, $options);
|
||||
$values[] = $value;
|
||||
}
|
||||
$replacements[$original] = implode('/', $values);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $replacements;
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\Component\Render\PlainTextOutput;
|
||||
use Drupal\Component\Transliteration\TransliterationInterface;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
|
||||
/**
|
||||
* Provides an alias cleaner.
|
||||
*/
|
||||
class AliasCleaner implements AliasCleanerInterface {
|
||||
|
||||
/**
|
||||
* The config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The alias storage helper.
|
||||
*
|
||||
* @var AliasStorageHelperInterface
|
||||
*/
|
||||
protected $aliasStorageHelper;
|
||||
|
||||
/**
|
||||
* Language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* Cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cacheBackend;
|
||||
|
||||
/**
|
||||
* Calculated settings cache.
|
||||
*
|
||||
* @todo Split this up into separate properties.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $cleanStringCache = array();
|
||||
|
||||
/**
|
||||
* Transliteration service.
|
||||
*
|
||||
* @var \Drupal\Component\Transliteration\TransliterationInterface
|
||||
*/
|
||||
protected $transliteration;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Creates a new AliasCleaner.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory.
|
||||
* @param \Drupal\pathauto\AliasStorageHelperInterface $alias_storage_helper
|
||||
* The alias storage helper.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* The cache backend.
|
||||
* @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration
|
||||
* The transliteration service.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, AliasStorageHelperInterface $alias_storage_helper, LanguageManagerInterface $language_manager, CacheBackendInterface $cache_backend, TransliterationInterface $transliteration, ModuleHandlerInterface $module_handler) {
|
||||
$this->configFactory = $config_factory;
|
||||
$this->aliasStorageHelper = $alias_storage_helper;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->cacheBackend = $cache_backend;
|
||||
$this->transliteration = $transliteration;
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cleanAlias($alias) {
|
||||
$config = $this->configFactory->get('pathauto.settings');
|
||||
$alias_max_length = min($config->get('max_length'), $this->aliasStorageHelper->getAliasSchemaMaxLength());
|
||||
|
||||
$output = $alias;
|
||||
|
||||
// Trim duplicate, leading, and trailing separators. Do this before cleaning
|
||||
// backslashes since a pattern like "[token1]/[token2]-[token3]/[token4]"
|
||||
// could end up like "value1/-/value2" and if backslashes were cleaned first
|
||||
// this would result in a duplicate blackslash.
|
||||
$output = $this->getCleanSeparators($output);
|
||||
|
||||
// Trim duplicate, leading, and trailing backslashes.
|
||||
$output = $this->getCleanSeparators($output, '/');
|
||||
|
||||
// Shorten to a logical place based on word boundaries.
|
||||
$output = Unicode::truncate($output, $alias_max_length, TRUE);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCleanSeparators($string, $separator = NULL) {
|
||||
$config = $this->configFactory->get('pathauto.settings');
|
||||
|
||||
if (!isset($separator)) {
|
||||
$separator = $config->get('separator');
|
||||
}
|
||||
|
||||
$output = $string;
|
||||
|
||||
if (strlen($separator)) {
|
||||
// Trim any leading or trailing separators.
|
||||
$output = trim($output, $separator);
|
||||
|
||||
// Escape the separator for use in regular expressions.
|
||||
$seppattern = preg_quote($separator, '/');
|
||||
|
||||
// Replace multiple separators with a single one.
|
||||
$output = preg_replace("/$seppattern+/", $separator, $output);
|
||||
|
||||
// Replace trailing separators around slashes.
|
||||
if ($separator !== '/') {
|
||||
$output = preg_replace("/\/+$seppattern\/+|$seppattern\/+|\/+$seppattern/", "/", $output);
|
||||
}
|
||||
else {
|
||||
// If the separator is a slash, we need to re-add the leading slash
|
||||
// dropped by the trim function.
|
||||
$output = '/' . $output;
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cleanString($string, array $options = array()) {
|
||||
if (empty($this->cleanStringCache)) {
|
||||
// Generate and cache variables used in this method.
|
||||
$config = $this->configFactory->get('pathauto.settings');
|
||||
$this->cleanStringCache = array(
|
||||
'separator' => $config->get('separator'),
|
||||
'strings' => array(),
|
||||
'transliterate' => $config->get('transliterate'),
|
||||
'punctuation' => array(),
|
||||
'reduce_ascii' => (bool) $config->get('reduce_ascii'),
|
||||
'ignore_words_regex' => FALSE,
|
||||
'lowercase' => (bool) $config->get('case'),
|
||||
'maxlength' => min($config->get('max_component_length'), $this->aliasStorageHelper->getAliasSchemaMaxLength()),
|
||||
);
|
||||
|
||||
// Generate and cache the punctuation replacements for strtr().
|
||||
$punctuation = $this->getPunctuationCharacters();
|
||||
foreach ($punctuation as $name => $details) {
|
||||
$action = $config->get('punctuation.' . $name);
|
||||
switch ($action) {
|
||||
case PathautoGeneratorInterface::PUNCTUATION_REMOVE:
|
||||
$this->cleanStringCache['punctuation'][$details['value']] = '';
|
||||
break;
|
||||
|
||||
case PathautoGeneratorInterface::PUNCTUATION_REPLACE:
|
||||
$this->cleanStringCache['punctuation'][$details['value']] = $this->cleanStringCache['separator'];
|
||||
break;
|
||||
|
||||
case PathautoGeneratorInterface::PUNCTUATION_DO_NOTHING:
|
||||
// Literally do nothing.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate and cache the ignored words regular expression.
|
||||
$ignore_words = $config->get('ignore_words');
|
||||
$ignore_words_regex = preg_replace(array('/^[,\s]+|[,\s]+$/', '/[,\s]+/'), array('', '\b|\b'), $ignore_words);
|
||||
if ($ignore_words_regex) {
|
||||
$this->cleanStringCache['ignore_words_regex'] = '\b' . $ignore_words_regex . '\b';
|
||||
if (function_exists('mb_eregi_replace')) {
|
||||
mb_regex_encoding('UTF-8');
|
||||
$this->cleanStringCache['ignore_words_callback'] = 'mb_eregi_replace';
|
||||
}
|
||||
else {
|
||||
$this->cleanStringCache['ignore_words_callback'] = 'preg_replace';
|
||||
$this->cleanStringCache['ignore_words_regex'] = '/' . $this->cleanStringCache['ignore_words_regex'] . '/i';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty strings do not need any processing.
|
||||
if ($string === '' || $string === NULL) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$langcode = NULL;
|
||||
if (!empty($options['language'])) {
|
||||
$langcode = $options['language']->getId();
|
||||
}
|
||||
elseif (!empty($options['langcode'])) {
|
||||
$langcode = $options['langcode'];
|
||||
}
|
||||
|
||||
// Check if the string has already been processed, and if so return the
|
||||
// cached result.
|
||||
if (isset($this->cleanStringCache['strings'][$langcode][(string) $string])) {
|
||||
return $this->cleanStringCache['strings'][$langcode][(string) $string];
|
||||
}
|
||||
|
||||
// Remove all HTML tags from the string.
|
||||
$output = Html::decodeEntities($string);
|
||||
$output = PlainTextOutput::renderFromHtml($output);
|
||||
|
||||
// Optionally transliterate.
|
||||
if ($this->cleanStringCache['transliterate']) {
|
||||
// If the reduce strings to letters and numbers is enabled, don't bother
|
||||
// replacing unknown characters with a question mark. Use an empty string
|
||||
// instead.
|
||||
$output = $this->transliteration->transliterate($output, $langcode, $this->cleanStringCache['reduce_ascii'] ? '' : '?');
|
||||
}
|
||||
|
||||
// Replace or drop punctuation based on user settings.
|
||||
$output = strtr($output, $this->cleanStringCache['punctuation']);
|
||||
|
||||
// Reduce strings to letters and numbers.
|
||||
if ($this->cleanStringCache['reduce_ascii']) {
|
||||
$output = preg_replace('/[^a-zA-Z0-9\/]+/', $this->cleanStringCache['separator'], $output);
|
||||
}
|
||||
|
||||
// Get rid of words that are on the ignore list.
|
||||
if ($this->cleanStringCache['ignore_words_regex']) {
|
||||
$words_removed = $this->cleanStringCache['ignore_words_callback']($this->cleanStringCache['ignore_words_regex'], '', $output);
|
||||
if (Unicode::strlen(trim($words_removed)) > 0) {
|
||||
$output = $words_removed;
|
||||
}
|
||||
}
|
||||
|
||||
// Always replace whitespace with the separator.
|
||||
$output = preg_replace('/\s+/', $this->cleanStringCache['separator'], $output);
|
||||
|
||||
// Trim duplicates and remove trailing and leading separators.
|
||||
$output = $this->getCleanSeparators($this->getCleanSeparators($output, $this->cleanStringCache['separator']));
|
||||
|
||||
// Optionally convert to lower case.
|
||||
if ($this->cleanStringCache['lowercase']) {
|
||||
$output = Unicode::strtolower($output);
|
||||
}
|
||||
|
||||
// Shorten to a logical place based on word boundaries.
|
||||
$output = Unicode::truncate($output, $this->cleanStringCache['maxlength'], TRUE);
|
||||
|
||||
// Cache this result in the static array.
|
||||
$this->cleanStringCache['strings'][$langcode][(string) $string] = $output;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPunctuationCharacters() {
|
||||
if (empty($this->punctuationCharacters)) {
|
||||
$langcode = $this->languageManager->getCurrentLanguage()->getId();
|
||||
|
||||
$cid = 'pathauto:punctuation:' . $langcode;
|
||||
if ($cache = $this->cacheBackend->get($cid)) {
|
||||
$this->punctuationCharacters = $cache->data;
|
||||
}
|
||||
else {
|
||||
$punctuation = array();
|
||||
$punctuation['double_quotes'] = array('value' => '"', 'name' => t('Double quotation marks'));
|
||||
$punctuation['quotes'] = array('value' => '\'', 'name' => t("Single quotation marks (apostrophe)"));
|
||||
$punctuation['backtick'] = array('value' => '`', 'name' => t('Back tick'));
|
||||
$punctuation['comma'] = array('value' => ',', 'name' => t('Comma'));
|
||||
$punctuation['period'] = array('value' => '.', 'name' => t('Period'));
|
||||
$punctuation['hyphen'] = array('value' => '-', 'name' => t('Hyphen'));
|
||||
$punctuation['underscore'] = array('value' => '_', 'name' => t('Underscore'));
|
||||
$punctuation['colon'] = array('value' => ':', 'name' => t('Colon'));
|
||||
$punctuation['semicolon'] = array('value' => ';', 'name' => t('Semicolon'));
|
||||
$punctuation['pipe'] = array('value' => '|', 'name' => t('Vertical bar (pipe)'));
|
||||
$punctuation['left_curly'] = array('value' => '{', 'name' => t('Left curly bracket'));
|
||||
$punctuation['left_square'] = array('value' => '[', 'name' => t('Left square bracket'));
|
||||
$punctuation['right_curly'] = array('value' => '}', 'name' => t('Right curly bracket'));
|
||||
$punctuation['right_square'] = array('value' => ']', 'name' => t('Right square bracket'));
|
||||
$punctuation['plus'] = array('value' => '+', 'name' => t('Plus sign'));
|
||||
$punctuation['equal'] = array('value' => '=', 'name' => t('Equal sign'));
|
||||
$punctuation['asterisk'] = array('value' => '*', 'name' => t('Asterisk'));
|
||||
$punctuation['ampersand'] = array('value' => '&', 'name' => t('Ampersand'));
|
||||
$punctuation['percent'] = array('value' => '%', 'name' => t('Percent sign'));
|
||||
$punctuation['caret'] = array('value' => '^', 'name' => t('Caret'));
|
||||
$punctuation['dollar'] = array('value' => '$', 'name' => t('Dollar sign'));
|
||||
$punctuation['hash'] = array('value' => '#', 'name' => t('Number sign (pound sign, hash)'));
|
||||
$punctuation['at'] = array('value' => '@', 'name' => t('At sign'));
|
||||
$punctuation['exclamation'] = array('value' => '!', 'name' => t('Exclamation mark'));
|
||||
$punctuation['tilde'] = array('value' => '~', 'name' => t('Tilde'));
|
||||
$punctuation['left_parenthesis'] = array('value' => '(', 'name' => t('Left parenthesis'));
|
||||
$punctuation['right_parenthesis'] = array('value' => ')', 'name' => t('Right parenthesis'));
|
||||
$punctuation['question_mark'] = array('value' => '?', 'name' => t('Question mark'));
|
||||
$punctuation['less_than'] = array('value' => '<', 'name' => t('Less-than sign'));
|
||||
$punctuation['greater_than'] = array('value' => '>', 'name' => t('Greater-than sign'));
|
||||
$punctuation['slash'] = array('value' => '/', 'name' => t('Slash'));
|
||||
$punctuation['back_slash'] = array('value' => '\\', 'name' => t('Backslash'));
|
||||
|
||||
// Allow modules to alter the punctuation list and cache the result.
|
||||
$this->moduleHandler->alter('pathauto_punctuation_chars', $punctuation);
|
||||
$this->cacheBackend->set($cid, $punctuation);
|
||||
$this->punctuationCharacters = $punctuation;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->punctuationCharacters;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cleanTokenValues(&$replacements, $data = array(), $options = array()) {
|
||||
foreach ($replacements as $token => $value) {
|
||||
// Only clean non-path tokens.
|
||||
if (!preg_match('/(path|alias|url|url-brief)\]$/', $token)) {
|
||||
$replacements[$token] = $this->cleanString($value, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetCaches() {
|
||||
$this->cleanStringCache = array();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
/**
|
||||
* @todo add class comment.
|
||||
*/
|
||||
interface AliasCleanerInterface {
|
||||
|
||||
/**
|
||||
* Clean up an URL alias.
|
||||
*
|
||||
* Performs the following alterations:
|
||||
* - Trim duplicate, leading, and trailing back-slashes.
|
||||
* - Trim duplicate, leading, and trailing separators.
|
||||
* - Shorten to a desired length and logical position based on word boundaries.
|
||||
*
|
||||
* @param string $alias
|
||||
* A string with the URL alias to clean up.
|
||||
*
|
||||
* @return string
|
||||
* The cleaned URL alias.
|
||||
*/
|
||||
public function cleanAlias($alias);
|
||||
|
||||
/**
|
||||
* Trims duplicate, leading, and trailing separators from a string.
|
||||
*
|
||||
* @param string $string
|
||||
* The string to clean path separators from.
|
||||
* @param string $separator
|
||||
* The path separator to use when cleaning.
|
||||
*
|
||||
* @return string
|
||||
* The cleaned version of the string.
|
||||
*
|
||||
* @see pathauto_cleanstring()
|
||||
* @see pathauto_clean_alias()
|
||||
*/
|
||||
public function getCleanSeparators($string, $separator = NULL);
|
||||
|
||||
/**
|
||||
* Clean up a string segment to be used in an URL alias.
|
||||
*
|
||||
* Performs the following possible alterations:
|
||||
* - Remove all HTML tags.
|
||||
* - Process the string through the transliteration module.
|
||||
* - Replace or remove punctuation with the separator character.
|
||||
* - Remove back-slashes.
|
||||
* - Replace non-ascii and non-numeric characters with the separator.
|
||||
* - Remove common words.
|
||||
* - Replace whitespace with the separator character.
|
||||
* - Trim duplicate, leading, and trailing separators.
|
||||
* - Convert to lower-case.
|
||||
* - Shorten to a desired length and logical position based on word boundaries.
|
||||
*
|
||||
* This function should *not* be called on URL alias or path strings
|
||||
* because it is assumed that they are already clean.
|
||||
*
|
||||
* @param string $string
|
||||
* A string to clean.
|
||||
* @param array $options
|
||||
* (optional) A keyed array of settings and flags to control the Pathauto
|
||||
* clean string replacement process. Supported options are:
|
||||
* - langcode: A language code to be used when translating strings.
|
||||
*
|
||||
* @return string
|
||||
* The cleaned string.
|
||||
*/
|
||||
public function cleanString($string, array $options = array());
|
||||
|
||||
/**
|
||||
* Return an array of arrays for punctuation values.
|
||||
*
|
||||
* Returns an array of arrays for punctuation values keyed by a name, including
|
||||
* the value and a textual description.
|
||||
* Can and should be expanded to include "all" non text punctuation values.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays for punctuation values keyed by a name, including the
|
||||
* value and a textual description.
|
||||
*/
|
||||
public function getPunctuationCharacters();
|
||||
|
||||
/**
|
||||
* Clean tokens so they are URL friendly.
|
||||
*
|
||||
* @param array $replacements
|
||||
* An array of token replacements
|
||||
* that need to be "cleaned" for use in the URL.
|
||||
* @param array $data
|
||||
* An array of objects used to generate the replacements.
|
||||
* @param array $options
|
||||
* An array of options used to generate the replacements.
|
||||
*/
|
||||
public function cleanTokenValues(&$replacements, $data = array(), $options = array());
|
||||
|
||||
/**
|
||||
* Resets internal caches.
|
||||
*/
|
||||
public function resetCaches();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Path\AliasStorageInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
|
||||
/**
|
||||
* Provides helper methods for accessing alias storage.
|
||||
*/
|
||||
class AliasStorageHelper implements AliasStorageHelperInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Alias schema max length.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $aliasSchemaMaxLength = 255;
|
||||
|
||||
/**
|
||||
* Config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The alias storage.
|
||||
*
|
||||
* @var \Drupal\Core\Path\AliasStorageInterface
|
||||
*/
|
||||
protected $aliasStorage;
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The messenger.
|
||||
*
|
||||
* @var \Drupal\pathauto\MessengerInterface
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* The config factory.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory.
|
||||
* @param \Drupal\Core\Path\AliasStorageInterface $alias_storage
|
||||
* The alias storage.
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database connection.
|
||||
* @param MessengerInterface $messenger
|
||||
* The messenger.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The string translation service.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, AliasStorageInterface $alias_storage, Connection $database, MessengerInterface $messenger, TranslationInterface $string_translation) {
|
||||
$this->configFactory = $config_factory;
|
||||
$this->aliasStorage = $alias_storage;
|
||||
$this->database = $database;
|
||||
$this->messenger = $messenger;
|
||||
$this->stringTranslation = $string_translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAliasSchemaMaxLength() {
|
||||
return $this->aliasSchemaMaxLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $path, $existing_alias = NULL, $op = NULL) {
|
||||
$config = $this->configFactory->get('pathauto.settings');
|
||||
|
||||
// Alert users if they are trying to create an alias that is the same as the
|
||||
// internal path.
|
||||
if ($path['source'] == $path['alias']) {
|
||||
$this->messenger->addMessage($this->t('Ignoring alias %alias because it is the same as the internal path.', array('%alias' => $path['alias'])));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Skip replacing the current alias with an identical alias.
|
||||
if (empty($existing_alias) || $existing_alias['alias'] != $path['alias']) {
|
||||
$path += array(
|
||||
'pathauto' => TRUE,
|
||||
'original' => $existing_alias,
|
||||
'pid' => NULL,
|
||||
);
|
||||
|
||||
// If there is already an alias, respect some update actions.
|
||||
if (!empty($existing_alias)) {
|
||||
switch ($config->get('update_action')) {
|
||||
case PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW:
|
||||
// Do not create the alias.
|
||||
return NULL;
|
||||
|
||||
case PathautoGeneratorInterface::UPDATE_ACTION_LEAVE:
|
||||
// Create a new alias instead of overwriting the existing by leaving
|
||||
// $path['pid'] empty.
|
||||
break;
|
||||
|
||||
case PathautoGeneratorInterface::UPDATE_ACTION_DELETE:
|
||||
// The delete actions should overwrite the existing alias.
|
||||
$path['pid'] = $existing_alias['pid'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Save the path array.
|
||||
$this->aliasStorage->save($path['source'], $path['alias'], $path['language'], $path['pid']);
|
||||
|
||||
if (!empty($existing_alias['pid'])) {
|
||||
$this->messenger->addMessage($this->t(
|
||||
'Created new alias %alias for %source, replacing %old_alias.',
|
||||
array(
|
||||
'%alias' => $path['alias'],
|
||||
'%source' => $path['source'],
|
||||
'%old_alias' => $existing_alias['alias'],
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
$this->messenger->addMessage($this->t('Created new alias %alias for %source.', array(
|
||||
'%alias' => $path['alias'],
|
||||
'%source' => $path['source'],
|
||||
)));
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadBySource($source, $language = LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
$alias = $this->aliasStorage->load([
|
||||
'source' => $source,
|
||||
'langcode' => $language,
|
||||
]);
|
||||
// If no alias was fetched and if a language was specified, fallbacks to
|
||||
// undefined language.
|
||||
if (!$alias && ($language !== LanguageInterface::LANGCODE_NOT_SPECIFIED)) {
|
||||
$alias = $this->aliasStorage->load([
|
||||
'source' => $source,
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
]);
|
||||
}
|
||||
return $alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteBySourcePrefix($source) {
|
||||
$pids = $this->loadBySourcePrefix($source);
|
||||
if ($pids) {
|
||||
$this->deleteMultiple($pids);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteAll() {
|
||||
$this->database->truncate('url_alias')->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteEntityPathAll(EntityInterface $entity, $default_uri = NULL) {
|
||||
$this->deleteBySourcePrefix('/' . $entity->toUrl('canonical')->getInternalPath());
|
||||
if (isset($default_uri) && $entity->toUrl('canonical')->toString() != $default_uri) {
|
||||
$this->deleteBySourcePrefix($default_uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadBySourcePrefix($source) {
|
||||
$select = $this->database->select('url_alias', 'u')
|
||||
->fields('u', array('pid'));
|
||||
|
||||
$or_group = $select->orConditionGroup()
|
||||
->condition('source', $source)
|
||||
->condition('source', rtrim($source, '/') . '/%', 'LIKE');
|
||||
|
||||
return $select
|
||||
->condition($or_group)
|
||||
->execute()
|
||||
->fetchCol();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function countBySourcePrefix($source) {
|
||||
$select = $this->database->select('url_alias', 'u')
|
||||
->fields('u', array('pid'));
|
||||
|
||||
$or_group = $select->orConditionGroup()
|
||||
->condition('source', $source)
|
||||
->condition('source', rtrim($source, '/') . '/%', 'LIKE');
|
||||
|
||||
return $select
|
||||
->condition($or_group)
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function countAll() {
|
||||
return $this->database->select('url_alias')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete multiple URL aliases.
|
||||
*
|
||||
* Intent of this is to abstract a potential path_delete_multiple() function
|
||||
* for Drupal 7 or 8.
|
||||
*
|
||||
* @param int[] $pids
|
||||
* An array of path IDs to delete.
|
||||
*/
|
||||
public function deleteMultiple($pids) {
|
||||
foreach ($pids as $pid) {
|
||||
$this->aliasStorage->delete(array('pid' => $pid));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
|
||||
/**
|
||||
* Provides helper methods for accessing alias storage.
|
||||
*/
|
||||
interface AliasStorageHelperInterface {
|
||||
|
||||
/**
|
||||
* Fetch the maximum length of the {url_alias}.alias field from the schema.
|
||||
*
|
||||
* @return int
|
||||
* An integer of the maximum URL alias length allowed by the database.
|
||||
*/
|
||||
public function getAliasSchemaMaxLength();
|
||||
|
||||
/**
|
||||
* Private function for Pathauto to create an alias.
|
||||
*
|
||||
* @param array $path
|
||||
* An associative array containing the following keys:
|
||||
* - source: The internal system path.
|
||||
* - alias: The URL alias.
|
||||
* - pid: (optional) Unique path alias identifier.
|
||||
* - language: (optional) The language of the alias.
|
||||
* @param array|bool|null $existing_alias
|
||||
* (optional) An associative array of the existing path alias.
|
||||
* @param string $op
|
||||
* An optional string with the operation being performed.
|
||||
*
|
||||
* @return array|bool
|
||||
* The saved path or NULL if the path was not saved.
|
||||
*/
|
||||
public function save(array $path, $existing_alias = NULL, $op = NULL);
|
||||
|
||||
/**
|
||||
* Fetches an existing URL alias given a path and optional language.
|
||||
*
|
||||
* @param string $source
|
||||
* An internal Drupal path.
|
||||
* @param string $language
|
||||
* An optional language code to look up the path in.
|
||||
*
|
||||
* @return bool|array
|
||||
* FALSE if no alias was found or an associative array containing the
|
||||
* following keys:
|
||||
* - source (string): The internal system path with a starting slash.
|
||||
* - alias (string): The URL alias with a starting slash.
|
||||
* - pid (int): Unique path alias identifier.
|
||||
* - langcode (string): The language code of the alias.
|
||||
*/
|
||||
public function loadBySource($source, $language = LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
|
||||
/**
|
||||
* Delete all aliases by source url.
|
||||
*
|
||||
* @param string $source
|
||||
* An internal Drupal path.
|
||||
*
|
||||
* @return bool
|
||||
* The URL alias source.
|
||||
*/
|
||||
public function deleteBySourcePrefix($source);
|
||||
|
||||
/**
|
||||
* Delete all aliases (truncate the url_alias table).
|
||||
*/
|
||||
public function deleteAll();
|
||||
|
||||
/**
|
||||
* Delete an entity URL alias and any of its sub-paths.
|
||||
*
|
||||
* This function also checks to see if the default entity URI is different
|
||||
* from the current entity URI and will delete any of the default aliases.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* An entity object.
|
||||
* @param string $default_uri
|
||||
* The optional default uri path for the entity.
|
||||
*/
|
||||
public function deleteEntityPathAll(EntityInterface $entity, $default_uri = NULL);
|
||||
|
||||
/**
|
||||
* Fetches an existing URL alias given a path prefix.
|
||||
*
|
||||
* @param string $source
|
||||
* An internal Drupal path prefix.
|
||||
*
|
||||
* @return integer[]
|
||||
* An array of PIDs.
|
||||
*/
|
||||
public function loadBySourcePrefix($source);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the count of url aliases for the source.
|
||||
*
|
||||
* @param $source
|
||||
* An internal Drupal path prefix.
|
||||
*
|
||||
* @return int
|
||||
* Number of url aliases for the source.
|
||||
*/
|
||||
public function countBySourcePrefix($source);
|
||||
|
||||
/**
|
||||
* Returns the total count of the url aliases.
|
||||
*
|
||||
* @return int
|
||||
* Total number of aliases.
|
||||
*/
|
||||
public function countAll();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
/**
|
||||
* Alias types that support batch updates and deletions.
|
||||
*/
|
||||
interface AliasTypeBatchUpdateInterface extends AliasTypeInterface {
|
||||
|
||||
/**
|
||||
* Gets called to batch update all entries.
|
||||
*
|
||||
* @param string $action
|
||||
* One of:
|
||||
* - 'create' to generate a URL alias for paths having none.
|
||||
* - 'update' to recreate the URL alias for paths already having one, useful if the pattern changed.
|
||||
* - 'all' to do both actions above at the same time.
|
||||
* @param array $context
|
||||
* Batch context.
|
||||
*/
|
||||
public function batchUpdate($action, &$context);
|
||||
|
||||
/**
|
||||
* Gets called to batch delete all aliases created by pathauto.
|
||||
*
|
||||
* @param array $context
|
||||
* Batch context.
|
||||
*/
|
||||
public function batchDelete(&$context);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
||||
use Drupal\Core\Plugin\ContextAwarePluginInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for pathauto alias types.
|
||||
*/
|
||||
interface AliasTypeInterface extends ContextAwarePluginInterface, DerivativeInspectionInterface {
|
||||
|
||||
/**
|
||||
* Get the label.
|
||||
*
|
||||
* @return string
|
||||
* The label.
|
||||
*/
|
||||
public function getLabel();
|
||||
|
||||
/**
|
||||
* Get the token types.
|
||||
*
|
||||
* @return string[]
|
||||
* The token types.
|
||||
*/
|
||||
public function getTokenTypes();
|
||||
|
||||
/**
|
||||
* Returns the source prefix; used for bulk delete.
|
||||
*
|
||||
* @return string
|
||||
* The source path prefix.
|
||||
*/
|
||||
public function getSourcePrefix();
|
||||
|
||||
/**
|
||||
* Determines if this plugin type can apply a given object.
|
||||
*
|
||||
* @param object $object
|
||||
* The object used to determine if this plugin can apply.
|
||||
*
|
||||
* @return bool
|
||||
* Whether this plugin applies to the given object.
|
||||
*/
|
||||
public function applies($object);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\Component\Plugin\FallbackPluginManagerInterface;
|
||||
|
||||
/**
|
||||
* Manages pathauto alias type plugins.
|
||||
*/
|
||||
class AliasTypeManager extends DefaultPluginManager implements FallbackPluginManagerInterface {
|
||||
|
||||
/**
|
||||
* Constructs a new AliasType manager instance.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct('Plugin/pathauto/AliasType', $namespaces, $module_handler, 'Drupal\pathauto\AliasTypeInterface', 'Drupal\pathauto\Annotation\AliasType');
|
||||
$this->alterInfo('pathauto_alias_types');
|
||||
$this->setCacheBackend($cache_backend, 'pathauto_alias_types');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns plugin definitions that support a given token type.
|
||||
*
|
||||
* @param string $type
|
||||
* The type of token plugin must support to be useful.
|
||||
*
|
||||
* @return array
|
||||
* Plugin definitions.
|
||||
*/
|
||||
public function getPluginDefinitionByType($type) {
|
||||
$definitions = array_filter($this->getDefinitions(), function ($definition) use ($type) {
|
||||
if (!empty($definition['types']) && in_array($type, $definition['types'])) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
});
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFallbackPluginId($plugin_id, array $configuration = array()) {
|
||||
return 'broken';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the definition of all visible plugins for this type.
|
||||
*
|
||||
* @return array
|
||||
* An array of plugin definitions (empty array if no definitions were
|
||||
* found). Keys are plugin IDs.
|
||||
*/
|
||||
public function getVisibleDefinitions() {
|
||||
$definitions = $this->getDefinitions();
|
||||
unset($definitions['broken']);
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Path\AliasManagerInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
|
||||
/**
|
||||
* Provides a utility for creating a unique path alias.
|
||||
*/
|
||||
class AliasUniquifier implements AliasUniquifierInterface {
|
||||
|
||||
/**
|
||||
* Config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The alias storage helper.
|
||||
*
|
||||
* @var \Drupal\pathauto\AliasStorageHelperInterface
|
||||
*/
|
||||
protected $aliasStorageHelper;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The route provider service.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteProviderInterface.
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* The alias manager.
|
||||
*
|
||||
* @var \Drupal\Core\Path\AliasManagerInterface
|
||||
*/
|
||||
protected $aliasManager;
|
||||
|
||||
/**
|
||||
* Creates a new AliasUniquifier.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory.
|
||||
* @param \Drupal\pathauto\AliasStorageHelperInterface $alias_storage_helper
|
||||
* The alias storage helper.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider service.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, AliasStorageHelperInterface $alias_storage_helper, ModuleHandlerInterface $module_handler, RouteProviderInterface $route_provider, AliasManagerInterface $alias_manager) {
|
||||
$this->configFactory = $config_factory;
|
||||
$this->aliasStorageHelper = $alias_storage_helper;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->routeProvider = $route_provider;
|
||||
$this->aliasManager = $alias_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function uniquify(&$alias, $source, $langcode) {
|
||||
$config = $this->configFactory->get('pathauto.settings');
|
||||
|
||||
if (!$this->isReserved($alias, $source, $langcode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the alias already exists, generate a new, hopefully unique, variant.
|
||||
$maxlength = min($config->get('max_length'), $this->aliasStorageHelper->getAliasSchemaMaxlength());
|
||||
$separator = $config->get('separator');
|
||||
$original_alias = $alias;
|
||||
|
||||
$i = 0;
|
||||
do {
|
||||
// Append an incrementing numeric suffix until we find a unique alias.
|
||||
$unique_suffix = $separator . $i;
|
||||
$alias = Unicode::truncate($original_alias, $maxlength - Unicode::strlen($unique_suffix), TRUE) . $unique_suffix;
|
||||
$i++;
|
||||
} while ($this->isReserved($alias, $source, $langcode));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isReserved($alias, $source, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
// Check if this alias already exists.
|
||||
if ($existing_source = $this->aliasManager->getPathByAlias($alias, $langcode)) {
|
||||
if ($existing_source != $alias) {
|
||||
// If it is an alias for the provided source, it is allowed to keep using
|
||||
// it. If not, then it is reserved.
|
||||
return $existing_source != $source;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Then check if there is a route with the same path.
|
||||
if ($this->isRoute($alias)) {
|
||||
return TRUE;
|
||||
}
|
||||
// Finally check if any other modules have reserved the alias.
|
||||
$args = array(
|
||||
$alias,
|
||||
$source,
|
||||
$langcode,
|
||||
);
|
||||
$implementations = $this->moduleHandler->getImplementations('pathauto_is_alias_reserved');
|
||||
foreach ($implementations as $module) {
|
||||
|
||||
$result = $this->moduleHandler->invoke($module, 'pathauto_is_alias_reserved', $args);
|
||||
|
||||
if (!empty($result)) {
|
||||
// As soon as the first module says that an alias is in fact reserved,
|
||||
// then there is no point in checking the rest of the modules.
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if the given path is a valid route.
|
||||
*
|
||||
* @param string $path
|
||||
* A string containing a relative path.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the path already exists.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function isRoute($path) {
|
||||
if (is_file(DRUPAL_ROOT . '/' . $path) || is_dir(DRUPAL_ROOT . '/' . $path)) {
|
||||
// Do not allow existing files or directories to get assigned an automatic
|
||||
// alias. Note that we do not need to use is_link() to check for symbolic
|
||||
// links since this returns TRUE for either is_file() or is_dir() already.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
$routes = $this->routeProvider->getRoutesByPattern($path);
|
||||
|
||||
// Only return true for an exact match, ignore placeholders.
|
||||
foreach ($routes as $route) {
|
||||
if ($route->getPath() == $path) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for alias uniquifiers.
|
||||
*/
|
||||
interface AliasUniquifierInterface {
|
||||
|
||||
/**
|
||||
* Check to ensure a path alias is unique and add suffix variants if necessary.
|
||||
*
|
||||
* Given an alias 'content/test' if a path alias with the exact alias already
|
||||
* exists, the function will change the alias to 'content/test-0' and will
|
||||
* increase the number suffix until it finds a unique alias.
|
||||
*
|
||||
* @param string $alias
|
||||
* A string with the alias. Can be altered by reference.
|
||||
* @param string $source
|
||||
* A string with the path source.
|
||||
* @param string $langcode
|
||||
* A string with a language code.
|
||||
*/
|
||||
public function uniquify(&$alias, $source, $langcode);
|
||||
|
||||
/**
|
||||
* Checks if an alias is reserved.
|
||||
*
|
||||
* @param string $alias
|
||||
* The alias.
|
||||
* @param string $source
|
||||
* The source.
|
||||
* @param string $langcode
|
||||
* (optional) The language code.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the alias is reserved.
|
||||
*/
|
||||
public function isReserved($alias, $source, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines an AliasType annotation.
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class AliasType extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The human-readable name of the action plugin.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* The token types.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $types = array();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Entity;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\ContextException;
|
||||
use Drupal\Core\Condition\ConditionPluginCollection;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Plugin\Context\Context;
|
||||
use Drupal\Core\Plugin\ContextAwarePluginInterface;
|
||||
use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
|
||||
use Drupal\pathauto\PathautoPatternInterface;
|
||||
|
||||
/**
|
||||
* Defines the Pathauto pattern entity.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "pathauto_pattern",
|
||||
* label = @Translation("Pathauto pattern"),
|
||||
* handlers = {
|
||||
* "list_builder" = "Drupal\pathauto\PathautoPatternListBuilder",
|
||||
* "form" = {
|
||||
* "default" = "Drupal\pathauto\Form\PatternEditForm",
|
||||
* "delete" = "Drupal\Core\Entity\EntityDeleteForm",
|
||||
* "enable" = "Drupal\pathauto\Form\PatternEnableForm",
|
||||
* "disable" = "Drupal\pathauto\Form\PatternDisableForm"
|
||||
* },
|
||||
* "route_provider" = {
|
||||
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
|
||||
* },
|
||||
* },
|
||||
* config_prefix = "pattern",
|
||||
* admin_permission = "administer pathauto",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "label",
|
||||
* "uuid" = "uuid",
|
||||
* "weight" = "weight",
|
||||
* "status" = "status"
|
||||
* },
|
||||
* config_export = {
|
||||
* "id",
|
||||
* "label",
|
||||
* "type",
|
||||
* "pattern",
|
||||
* "selection_criteria",
|
||||
* "selection_logic",
|
||||
* "weight",
|
||||
* "relationships"
|
||||
* },
|
||||
* lookup_keys = {
|
||||
* "type",
|
||||
* "status",
|
||||
* },
|
||||
* links = {
|
||||
* "collection" = "/admin/config/search/path/patterns",
|
||||
* "edit-form" = "/admin/config/search/path/patterns/{pathauto_pattern}",
|
||||
* "delete-form" = "/admin/config/search/path/patterns/{pathauto_pattern}/delete",
|
||||
* "enable" = "/admin/config/search/path/patterns/{pathauto_pattern}/enable",
|
||||
* "disable" = "/admin/config/search/path/patterns/{pathauto_pattern}/disable"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class PathautoPattern extends ConfigEntityBase implements PathautoPatternInterface {
|
||||
|
||||
/**
|
||||
* The Pathauto pattern ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The Pathauto pattern label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* The pattern type.
|
||||
*
|
||||
* A string denoting the type of pathauto pattern this is. For a node path
|
||||
* this would be 'node', for users it would be 'user', and so on. This allows
|
||||
* for arbitrary non-entity patterns to be possible if applicable.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Plugin\DefaultSingleLazyPluginCollection
|
||||
*/
|
||||
protected $aliasTypeCollection;
|
||||
|
||||
/**
|
||||
* A tokenized string for alias generation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pattern;
|
||||
|
||||
/**
|
||||
* The plugin configuration for the selection criteria condition plugins.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $selection_criteria = [];
|
||||
|
||||
/**
|
||||
* The selection logic for this pattern entity (either 'and' or 'or').
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selection_logic = 'and';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $weight = 0;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
* Keys are context tokens, and values are arrays with the following keys:
|
||||
* - label (string|null, optional): The human-readable label of this
|
||||
* relationship.
|
||||
*/
|
||||
protected $relationships = [];
|
||||
|
||||
/**
|
||||
* The plugin collection that holds the selection criteria condition plugins.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\LazyPluginCollection
|
||||
*/
|
||||
protected $selectionConditionCollection;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Not using core's default logic around ConditionPluginCollection since it
|
||||
* incorrectly assumes no condition will ever be applied twice.
|
||||
*/
|
||||
public function preSave(EntityStorageInterface $storage) {
|
||||
parent::preSave($storage);
|
||||
$criteria = [];
|
||||
foreach ($this->getSelectionConditions() as $id => $condition) {
|
||||
$criteria[$id] = $condition->getConfiguration();
|
||||
}
|
||||
$this->selection_criteria = $criteria;
|
||||
|
||||
// Invalidate the static caches.
|
||||
\Drupal::service('pathauto.generator')->resetCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::postDelete($storage, $entities);
|
||||
// Invalidate the static caches.
|
||||
\Drupal::service('pathauto.generator')->resetCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
parent::calculateDependencies();
|
||||
|
||||
$this->calculatePluginDependencies($this->getAliasType());
|
||||
|
||||
foreach ($this->getSelectionConditions() as $instance) {
|
||||
$this->calculatePluginDependencies($instance);
|
||||
}
|
||||
|
||||
return $this->getDependencies();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPattern() {
|
||||
return $this->pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPattern($pattern) {
|
||||
$this->pattern = $pattern;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getType() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAliasType() {
|
||||
if (!$this->aliasTypeCollection) {
|
||||
$this->aliasTypeCollection = new DefaultSingleLazyPluginCollection(\Drupal::service('plugin.manager.alias_type'), $this->getType(), ['default' => $this->getPattern()]);
|
||||
}
|
||||
return $this->aliasTypeCollection->get($this->getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWeight() {
|
||||
return $this->weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setWeight($weight) {
|
||||
$this->weight = $weight;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContexts() {
|
||||
$contexts = $this->getAliasType()->getContexts();
|
||||
foreach ($this->getRelationships() as $token => $definition) {
|
||||
/** @var \Drupal\ctools\TypedDataResolver $resolver */
|
||||
$resolver = \Drupal::service('ctools.typed_data.resolver');
|
||||
$context = $resolver->convertTokenToContext($token, $contexts);
|
||||
$context_definition = $context->getContextDefinition();
|
||||
if (!empty($definition['label'])) {
|
||||
$context_definition->setLabel($definition['label']);
|
||||
}
|
||||
$contexts[$token] = $context;
|
||||
}
|
||||
return $contexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasRelationship($token) {
|
||||
return isset($this->relationships[$token]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addRelationship($token, $label = NULL) {
|
||||
if (!$this->hasRelationship($token)) {
|
||||
$this->relationships[$token] = [
|
||||
'label' => $label,
|
||||
];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function replaceRelationship($token, $label) {
|
||||
if ($this->hasRelationship($token)) {
|
||||
$this->relationships[$token] = [
|
||||
'label' => $label,
|
||||
];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeRelationship($token) {
|
||||
unset($this->relationships[$token]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRelationships() {
|
||||
return $this->relationships;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSelectionConditions() {
|
||||
if (!$this->selectionConditionCollection) {
|
||||
$this->selectionConditionCollection = new ConditionPluginCollection(\Drupal::service('plugin.manager.condition'), $this->get('selection_criteria'));
|
||||
}
|
||||
return $this->selectionConditionCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addSelectionCondition(array $configuration) {
|
||||
$configuration['uuid'] = $this->uuidGenerator()->generate();
|
||||
$this->getSelectionConditions()->addInstanceId($configuration['uuid'], $configuration);
|
||||
return $configuration['uuid'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSelectionCondition($condition_id) {
|
||||
return $this->getSelectionConditions()->get($condition_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeSelectionCondition($condition_id) {
|
||||
$this->getSelectionConditions()->removeInstanceId($condition_id);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSelectionLogic() {
|
||||
return $this->selection_logic;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies($object) {
|
||||
if ($this->getAliasType()->applies($object)) {
|
||||
$definitions = $this->getAliasType()->getContextDefinitions();
|
||||
if (count($definitions) > 1) {
|
||||
throw new \Exception("Alias types do not support more than one context.");
|
||||
}
|
||||
$keys = array_keys($definitions);
|
||||
// Set the context object on our Alias plugin before retrieving contexts.
|
||||
$this->getAliasType()->setContextValue($keys[0], $object);
|
||||
/** @var \Drupal\Core\Plugin\Context\ContextInterface[] $base_contexts */
|
||||
$contexts = $this->getContexts();
|
||||
/** @var \Drupal\Core\Plugin\Context\ContextHandler $context_handler */
|
||||
$context_handler = \Drupal::service('context.handler');
|
||||
$conditions = $this->getSelectionConditions();
|
||||
foreach ($conditions as $condition) {
|
||||
|
||||
// As the context object is kept and only the value is switched out,
|
||||
// it can over time grow to a huge number of cache contexts. Reset it
|
||||
// if there are 100 cache tags to prevent cache tag merging getting too
|
||||
// slow.
|
||||
foreach ($condition->getContextDefinitions() as $name => $context_definition) {
|
||||
if (count($condition->getContext($name)->getCacheTags()) > 100) {
|
||||
$condition->setContext($name, new Context($context_definition));
|
||||
}
|
||||
}
|
||||
|
||||
if ($condition instanceof ContextAwarePluginInterface) {
|
||||
try {
|
||||
$context_handler->applyContextMapping($condition, $contexts);
|
||||
}
|
||||
catch (ContextException $e) {
|
||||
watchdog_exception('pathauto', $e);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
$result = $condition->execute();
|
||||
if ($this->getSelectionLogic() == 'and' && !$result) {
|
||||
return FALSE;
|
||||
}
|
||||
elseif ($this->getSelectionLogic() == 'or' && $result) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Config\ConfigCrudEvent;
|
||||
use Drupal\Core\Config\ConfigEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\pathauto\AliasTypeManager;
|
||||
|
||||
/**
|
||||
* A subscriber to clear fielddefinition cache when saving pathauto settings.
|
||||
*/
|
||||
class PathautoSettingsCacheTag implements EventSubscriberInterface {
|
||||
|
||||
protected $entityFieldManager;
|
||||
protected $aliasTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a PathautoSettingsCacheTag object.
|
||||
*/
|
||||
public function __construct(EntityFieldManagerInterface $entity_field_manager, AliasTypeManager $alias_type_manager) {
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
$this->aliasTypeManager = $alias_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the 'rendered' cache tag whenever the settings are modified.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigCrudEvent $event
|
||||
* The Event to process.
|
||||
*/
|
||||
public function onSave(ConfigCrudEvent $event) {
|
||||
if ($event->getConfig()->getName() === 'pathauto.settings') {
|
||||
$config = $event->getConfig();
|
||||
$original_entity_types = $config->getOriginal('enabled_entity_types');
|
||||
|
||||
// Clear cached field definitions if the values are changed.
|
||||
if ($original_entity_types != $config->get('enabled_entity_types')) {
|
||||
$this->entityFieldManager->clearCachedFieldDefinitions();
|
||||
$this->aliasTypeManager->clearCachedDefinitions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[ConfigEvents::SAVE][] = ['onSave'];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\pathauto\AliasTypeManager;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Alias mass delete form.
|
||||
*/
|
||||
class PathautoAdminDelete extends FormBase {
|
||||
|
||||
/**
|
||||
* The alias type manager.
|
||||
*
|
||||
* @var \Drupal\pathauto\AliasTypeManager
|
||||
*/
|
||||
protected $aliasTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a PathautoAdminDelete object.
|
||||
*
|
||||
* @param \Drupal\pathauto\AliasTypeManager $alias_type_manager
|
||||
* The alias type manager.
|
||||
*/
|
||||
public function __construct(AliasTypeManager $alias_type_manager) {
|
||||
$this->aliasTypeManager = $alias_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin.manager.alias_type')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'pathauto_admin_delete';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['delete'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Choose aliases to delete'),
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
|
||||
// First we do the "all" case.
|
||||
$storage_helper = \Drupal::service('pathauto.alias_storage_helper');
|
||||
$total_count = $storage_helper->countAll();
|
||||
$form['delete']['all_aliases'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('All aliases'),
|
||||
'#default_value' => FALSE,
|
||||
'#description' => $this->t('Delete all aliases. Number of aliases which will be deleted: %count.', ['%count' => $total_count]),
|
||||
];
|
||||
|
||||
// Next, iterate over all visible alias types.
|
||||
$definitions = $this->aliasTypeManager->getVisibleDefinitions();
|
||||
|
||||
foreach ($definitions as $id => $definition) {
|
||||
/** @var \Drupal\pathauto\AliasTypeInterface $alias_type */
|
||||
$alias_type = $this->aliasTypeManager->createInstance($id);
|
||||
$count = $storage_helper->countBySourcePrefix($alias_type->getSourcePrefix());
|
||||
$form['delete']['plugins'][$id] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => (string) $definition['label'],
|
||||
'#default_value' => FALSE,
|
||||
'#description' => $this->t('Delete aliases for all @label. Number of aliases which will be deleted: %count.', ['@label' => (string) $definition['label'], '%count' => $count]),
|
||||
];
|
||||
}
|
||||
|
||||
$form['options'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Delete options'),
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
|
||||
// Provide checkbox for not deleting custom aliases.
|
||||
$form['options']['keep_custom_aliases'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Only delete automatically generated aliases'),
|
||||
'#default_value' => TRUE,
|
||||
'#description' => $this->t('When checked, aliases which have been manually set are not affected by this mass-deletion.'),
|
||||
];
|
||||
|
||||
// Warn them and give a button that shows we mean business.
|
||||
$form['warning'] = ['#value' => '<p>' . $this->t('<strong>Note:</strong> there is no confirmation. Be sure of your action before clicking the "Delete aliases now!" button.<br />You may want to make a backup of the database and/or the url_alias table prior to using this feature.') . '</p>'];
|
||||
$form['buttons']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Delete aliases now!'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$delete_all = $form_state->getValue(['delete', 'all_aliases']);
|
||||
// Keeping custom aliases forces us to go the slow way to correctly check
|
||||
// the automatic/manual flag.
|
||||
if ($form_state->getValue(['options', 'keep_custom_aliases'])) {
|
||||
$batch = [
|
||||
'title' => $this->t('Bulk deleting URL aliases'),
|
||||
'operations' => [['Drupal\pathauto\Form\PathautoAdminDelete::batchStart', [$delete_all]]],
|
||||
'finished' => 'Drupal\pathauto\Form\PathautoAdminDelete::batchFinished',
|
||||
];
|
||||
|
||||
if ($delete_all) {
|
||||
foreach (array_keys($form_state->getValue(['delete', 'plugins'])) as $id) {
|
||||
$batch['operations'][] = ['Drupal\pathauto\Form\PathautoAdminDelete::batchProcess', [$id]];
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach (array_keys(array_filter($form_state->getValue(['delete', 'plugins']))) as $id) {
|
||||
$batch['operations'][] = ['Drupal\pathauto\Form\PathautoAdminDelete::batchProcess', [$id]];
|
||||
}
|
||||
}
|
||||
|
||||
batch_set($batch);
|
||||
}
|
||||
else if ($delete_all) {
|
||||
\Drupal::service('pathauto.alias_storage_helper')->deleteAll();
|
||||
drupal_set_message($this->t('All of your path aliases have been deleted.'));
|
||||
}
|
||||
else {
|
||||
$storage_helper = \Drupal::service('pathauto.alias_storage_helper');
|
||||
foreach (array_keys(array_filter($form_state->getValue(['delete', 'plugins']))) as $id) {
|
||||
$alias_type = $this->aliasTypeManager->createInstance($id);
|
||||
$storage_helper->deleteBySourcePrefix((string) $alias_type->getSourcePrefix());
|
||||
drupal_set_message($this->t('All of your %label path aliases have been deleted.', ['%label' => $alias_type->getLabel()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch callback; record if aliases of all types must be deleted.
|
||||
*/
|
||||
public static function batchStart($delete_all, &$context) {
|
||||
$context['results']['delete_all'] = $delete_all;
|
||||
$context['results']['deletions'] = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Common batch processing callback for all operations.
|
||||
*/
|
||||
public static function batchProcess($id, &$context) {
|
||||
/** @var \Drupal\pathauto\AliasTypeBatchUpdateInterface $alias_type */
|
||||
$alias_type = \Drupal::service('plugin.manager.alias_type')->createInstance($id);
|
||||
$alias_type->batchDelete($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch finished callback.
|
||||
*/
|
||||
public static function batchFinished($success, $results, $operations) {
|
||||
if ($success) {
|
||||
if ($results['delete_all']) {
|
||||
drupal_set_message(t('All of your automatically generated path aliases have been deleted.'));
|
||||
}
|
||||
else if (isset($results['deletions'])) {
|
||||
foreach (array_values($results['deletions']) as $label) {
|
||||
drupal_set_message(t('All of your automatically generated %label path aliases have been deleted.', ['%label' => $label]));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$error_operation = reset($operations);
|
||||
drupal_set_message(t('An error occurred while processing @operation with arguments : @args', array('@operation' => $error_operation[0], '@args' => print_r($error_operation[0], TRUE))));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\pathauto\AliasTypeBatchUpdateInterface;
|
||||
use Drupal\pathauto\AliasTypeManager;
|
||||
use Drupal\pathauto\PathautoGeneratorInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Configure file system settings for this site.
|
||||
*/
|
||||
class PathautoBulkUpdateForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The alias type manager.
|
||||
*
|
||||
* @var \Drupal\pathauto\AliasTypeManager
|
||||
*/
|
||||
protected $aliasTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a PathautoBulkUpdateForm object.
|
||||
*
|
||||
* @param \Drupal\pathauto\AliasTypeManager $alias_type_manager
|
||||
* The alias type manager.
|
||||
*/
|
||||
public function __construct(AliasTypeManager $alias_type_manager) {
|
||||
$this->aliasTypeManager = $alias_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin.manager.alias_type')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'pathauto_bulk_update_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
|
||||
$form = array();
|
||||
|
||||
$form['#update_callbacks'] = array();
|
||||
|
||||
$form['update'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Select the types of paths for which to generate URL aliases'),
|
||||
'#options' => array(),
|
||||
'#default_value' => array(),
|
||||
);
|
||||
|
||||
$definitions = $this->aliasTypeManager->getVisibleDefinitions();
|
||||
|
||||
foreach ($definitions as $id => $definition) {
|
||||
$alias_type = $this->aliasTypeManager->createInstance($id);
|
||||
if ($alias_type instanceof AliasTypeBatchUpdateInterface) {
|
||||
$form['update']['#options'][$id] = $alias_type->getLabel();
|
||||
}
|
||||
}
|
||||
|
||||
$form['action'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Select which URL aliases to generate'),
|
||||
'#options' => ['create' => $this->t('Generate a URL alias for un-aliased paths only')],
|
||||
'#default_value' => 'create',
|
||||
);
|
||||
|
||||
$config = $this->config('pathauto.settings');
|
||||
|
||||
if ($config->get('update_action') == PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW) {
|
||||
// Existing aliases should not be updated.
|
||||
$form['warning'] = array(
|
||||
'#markup' => $this->t('<a href=":url">Pathauto settings</a> are set to ignore paths which already have a URL alias. You can only create URL aliases for paths having none.', [':url' => Url::fromRoute('pathauto.settings.form')->toString()]),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$form['action']['#options']['update'] = $this->t('Update the URL alias for paths having an old URL alias');
|
||||
$form['action']['#options']['all'] = $this->t('Regenerate URL aliases for all paths');
|
||||
}
|
||||
|
||||
$form['actions']['#type'] = 'actions';
|
||||
$form['actions']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Update'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$batch = array(
|
||||
'title' => $this->t('Bulk updating URL aliases'),
|
||||
'operations' => array(
|
||||
array('Drupal\pathauto\Form\PathautoBulkUpdateForm::batchStart', array()),
|
||||
),
|
||||
'finished' => 'Drupal\pathauto\Form\PathautoBulkUpdateForm::batchFinished',
|
||||
);
|
||||
|
||||
$action = $form_state->getValue('action');
|
||||
|
||||
foreach ($form_state->getValue('update') as $id) {
|
||||
if (!empty($id)) {
|
||||
$batch['operations'][] = array('Drupal\pathauto\Form\PathautoBulkUpdateForm::batchProcess', [$id, $action]);
|
||||
}
|
||||
}
|
||||
|
||||
batch_set($batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch callback; initialize the number of updated aliases.
|
||||
*/
|
||||
public static function batchStart(&$context) {
|
||||
$context['results']['updates'] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common batch processing callback for all operations.
|
||||
*
|
||||
* Required to load our include the proper batch file.
|
||||
*/
|
||||
public static function batchProcess($id, $action, &$context) {
|
||||
/** @var \Drupal\pathauto\AliasTypeBatchUpdateInterface $alias_type */
|
||||
$alias_type = \Drupal::service('plugin.manager.alias_type')->createInstance($id);
|
||||
$alias_type->batchUpdate($action, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch finished callback.
|
||||
*/
|
||||
public static function batchFinished($success, $results, $operations) {
|
||||
if ($success) {
|
||||
if ($results['updates']) {
|
||||
drupal_set_message(\Drupal::translation()->formatPlural($results['updates'], 'Generated 1 URL alias.', 'Generated @count URL aliases.'));
|
||||
}
|
||||
else {
|
||||
drupal_set_message(t('No new URL aliases to generate.'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
$error_operation = reset($operations);
|
||||
drupal_set_message(t('An error occurred while processing @operation with arguments : @args', array('@operation' => $error_operation[0], '@args' => print_r($error_operation[0], TRUE))));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Form;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\pathauto\AliasTypeManager;
|
||||
use Drupal\pathauto\PathautoGeneratorInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Configure file system settings for this site.
|
||||
*/
|
||||
class PathautoSettingsForm extends ConfigFormBase {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\pathauto\AliasTypeManager
|
||||
*/
|
||||
protected $aliasTypeManager;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, AliasTypeManager $alias_type_manager) {
|
||||
parent::__construct($config_factory);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
$this->aliasTypeManager = $alias_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('config.factory'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_field.manager'),
|
||||
$container->get('plugin.manager.alias_type')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'pathauto_settings_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return ['pathauto.settings'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$config = $this->config('pathauto.settings');
|
||||
|
||||
$form['enabled_entity_types'] = [
|
||||
'#type' => 'details',
|
||||
'#open' => TRUE,
|
||||
'#title' => $this->t('Enabled entity types'),
|
||||
'#description' => $this->t('Enable to add a path field and allow to define alias patterns for the given type. Disabled types already define a path field themselves or currently have a pattern.'),
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
|
||||
// Get all applicable entity types.
|
||||
foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
|
||||
// Disable a checkbox if it already exists and if the entity type has
|
||||
// patterns currently defined or if it isn't defined by us.
|
||||
$patterns_count = \Drupal::entityQuery('pathauto_pattern')
|
||||
->condition('type', 'canonical_entities:' . $entity_type_id)
|
||||
->count()
|
||||
->execute();
|
||||
|
||||
if (is_subclass_of($entity_type->getClass(), FieldableEntityInterface::class) && $entity_type->hasLinkTemplate('canonical')) {
|
||||
$field_definitions = $this->entityFieldManager->getBaseFieldDefinitions($entity_type_id);
|
||||
$form['enabled_entity_types'][$entity_type_id] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $entity_type->getLabel(),
|
||||
'#default_value' => isset($field_definitions['path']) || in_array($entity_type_id, $config->get('enabled_entity_types')),
|
||||
'#disabled' => isset($field_definitions['path']) && ($field_definitions['path']->getProvider() != 'pathauto' || $patterns_count),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$form['verbose'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Verbose'),
|
||||
'#default_value' => $config->get('verbose'),
|
||||
'#description' => $this->t('Display alias changes (except during bulk updates).'),
|
||||
);
|
||||
|
||||
$form['separator'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Separator'),
|
||||
'#size' => 1,
|
||||
'#maxlength' => 1,
|
||||
'#default_value' => $config->get('separator'),
|
||||
'#description' => $this->t('Character used to separate words in titles. This will replace any spaces and punctuation characters. Using a space or + character can cause unexpected results.'),
|
||||
);
|
||||
|
||||
$form['case'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Character case'),
|
||||
'#default_value' => $config->get('case'),
|
||||
'#description' => $this->t('Convert token values to lowercase.'),
|
||||
);
|
||||
|
||||
$max_length = \Drupal::service('pathauto.alias_storage_helper')->getAliasSchemaMaxlength();
|
||||
|
||||
$help_link = '';
|
||||
if (\Drupal::moduleHandler()->moduleExists('help')) {
|
||||
$help_link = ' ' . $this->t('See <a href=":pathauto-help">Pathauto help</a> for details.', [':pathauto-help' => Url::fromRoute('help.page', ['name' => 'pathauto'])->toString()]);
|
||||
}
|
||||
|
||||
$form['max_length'] = array(
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Maximum alias length'),
|
||||
'#size' => 3,
|
||||
'#maxlength' => 3,
|
||||
'#default_value' => $config->get('max_length'),
|
||||
'#min' => 1,
|
||||
'#max' => $max_length,
|
||||
'#description' => $this->t('Maximum length of aliases to generate. 100 is the recommended length. @max is the maximum possible length.', array('@max' => $max_length)) . $help_link,
|
||||
);
|
||||
|
||||
$form['max_component_length'] = array(
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Maximum component length'),
|
||||
'#size' => 3,
|
||||
'#maxlength' => 3,
|
||||
'#default_value' => $config->get('max_component_length'),
|
||||
'#min' => 1,
|
||||
'#max' => $max_length,
|
||||
'#description' => $this->t('Maximum text length of any component in the alias (e.g., [title]). 100 is the recommended length. @max is the maximum possible length.', ['@max' => $max_length]) . $help_link,
|
||||
);
|
||||
|
||||
$description = $this->t('What should Pathauto do when updating an existing content item which already has an alias?');
|
||||
if (\Drupal::moduleHandler()->moduleExists('redirect')) {
|
||||
$description .= ' ' . $this->t('The <a href=":url">Redirect module settings</a> affect whether a redirect is created when an alias is deleted.', array(':url' => Url::fromRoute('redirect.settings')->toString()));
|
||||
}
|
||||
else {
|
||||
$description .= ' ' . $this->t('Considering installing the <a href=":url">Redirect module</a> to get redirects when your aliases change.', array(':url' => 'http://drupal.org/project/redirect'));
|
||||
}
|
||||
|
||||
$form['update_action'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Update action'),
|
||||
'#default_value' => $config->get('update_action'),
|
||||
'#options' => array(
|
||||
PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW => $this->t('Do nothing. Leave the old alias intact.'),
|
||||
PathautoGeneratorInterface::UPDATE_ACTION_LEAVE => $this->t('Create a new alias. Leave the existing alias functioning.'),
|
||||
PathautoGeneratorInterface::UPDATE_ACTION_DELETE => $this->t('Create a new alias. Delete the old alias.'),
|
||||
),
|
||||
'#description' => $description,
|
||||
);
|
||||
|
||||
$form['transliterate'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Transliterate prior to creating alias'),
|
||||
'#default_value' => $config->get('transliterate'),
|
||||
'#description' => $this->t('When a pattern includes certain characters (such as those with accents) should Pathauto attempt to transliterate them into the US-ASCII alphabet?'),
|
||||
);
|
||||
|
||||
$form['reduce_ascii'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Reduce strings to letters and numbers'),
|
||||
'#default_value' => $config->get('reduce_ascii'),
|
||||
'#description' => $this->t('Filters the new alias to only letters and numbers found in the ASCII-96 set.'),
|
||||
);
|
||||
|
||||
$form['ignore_words'] = array(
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Strings to Remove'),
|
||||
'#default_value' => $config->get('ignore_words'),
|
||||
'#description' => $this->t('Words to strip out of the URL alias, separated by commas. Do not use this to remove punctuation.'),
|
||||
'#wysiwyg' => FALSE,
|
||||
);
|
||||
|
||||
$form['punctuation'] = array(
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Punctuation'),
|
||||
'#open' => FALSE,
|
||||
'#tree' => TRUE,
|
||||
);
|
||||
|
||||
$punctuation = \Drupal::service('pathauto.alias_cleaner')->getPunctuationCharacters();
|
||||
|
||||
foreach ($punctuation as $name => $details) {
|
||||
// Use the value from config if it exists.
|
||||
if ($config->get('punctuation.' . $name) !== NULL) {
|
||||
$details['default'] = $config->get('punctuation.' . $name);
|
||||
}
|
||||
else {
|
||||
// Otherwise use the correct default.
|
||||
$details['default'] = $details['value'] == $config->get('separator') ? PathautoGeneratorInterface::PUNCTUATION_REPLACE : PathautoGeneratorInterface::PUNCTUATION_REMOVE;
|
||||
}
|
||||
$form['punctuation'][$name] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => $details['name'] . ' (<code>' . Html::escape($details['value']) . '</code>)',
|
||||
'#default_value' => $details['default'],
|
||||
'#options' => array(
|
||||
PathautoGeneratorInterface::PUNCTUATION_REMOVE => $this->t('Remove'),
|
||||
PathautoGeneratorInterface::PUNCTUATION_REPLACE => $this->t('Replace by separator'),
|
||||
PathautoGeneratorInterface::PUNCTUATION_DO_NOTHING => $this->t('No action (do not replace)'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
$config = $this->config('pathauto.settings');
|
||||
|
||||
$form_state->cleanValues();
|
||||
|
||||
foreach ($form_state->getValues() as $key => $value) {
|
||||
if ($key == 'enabled_entity_types') {
|
||||
$enabled_entity_types = [];
|
||||
foreach ($value as $entity_type_id => $enabled) {
|
||||
$field_definitions = $this->entityFieldManager->getBaseFieldDefinitions($entity_type_id);
|
||||
// Verify that the entity type is enabled and that it is not defined
|
||||
// or defined by us before adding it to the configuration, so that
|
||||
// we do not store an entity type that cannot be enabled or disabled.
|
||||
if ($enabled && (!isset($field_definitions['path']) || ($field_definitions['path']->getProvider() === 'pathauto'))) {
|
||||
$enabled_entity_types[] = $entity_type_id;
|
||||
}
|
||||
}
|
||||
$value = $enabled_entity_types;
|
||||
}
|
||||
$config->set($key, $value);
|
||||
}
|
||||
$config->save();
|
||||
|
||||
parent::submitForm($form, $form_state);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides the pathauto pattern disable disable form.
|
||||
*/
|
||||
class PatternDisableForm extends EntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to disable the pattern %label?', array('%label' => $this->entity->label()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.pathauto_pattern.collection');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Disable');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->t('Disabled patterns are ignored when generating aliases.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->entity->disable()->save();
|
||||
drupal_set_message($this->t('Disabled pattern %label.', array('%label' => $this->entity->label())));
|
||||
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\pathauto\AliasTypeManager;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Edit form for pathauto patterns.
|
||||
*/
|
||||
class PatternEditForm extends EntityForm {
|
||||
|
||||
/**
|
||||
* @var \Drupal\pathauto\AliasTypeManager
|
||||
*/
|
||||
protected $manager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\pathauto\PathautoPatternInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* The entity type bundle info service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $entityTypeBundleInfo;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin.manager.alias_type'),
|
||||
$container->get('entity_type.bundle.info'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('language_manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* PatternEditForm constructor.
|
||||
*
|
||||
* @param \Drupal\pathauto\AliasTypeManager $manager
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
*/
|
||||
function __construct(AliasTypeManager $manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityTypeManagerInterface $entity_type_manager, LanguageManagerInterface $language_manager) {
|
||||
$this->manager = $manager;
|
||||
$this->entityTypeBundleInfo = $entity_type_bundle_info;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->languageManager = $language_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
|
||||
$options = [];
|
||||
foreach ($this->manager->getVisibleDefinitions() as $plugin_id => $plugin_definition) {
|
||||
$options[$plugin_id] = $plugin_definition['label'];
|
||||
}
|
||||
$form['type'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Pattern type'),
|
||||
'#default_value' => $this->entity->getType(),
|
||||
'#options' => $options,
|
||||
'#required' => TRUE,
|
||||
'#limit_validation_errors' => array(array('type')),
|
||||
'#submit' => array('::submitSelectType'),
|
||||
'#executes_submit_callback' => TRUE,
|
||||
'#ajax' => array(
|
||||
'callback' => '::ajaxReplacePatternForm',
|
||||
'wrapper' => 'pathauto-pattern',
|
||||
'method' => 'replace',
|
||||
),
|
||||
];
|
||||
|
||||
$form['pattern_container'] = [
|
||||
'#type' => 'container',
|
||||
'#prefix' => '<div id="pathauto-pattern">',
|
||||
'#suffix' => '</div>',
|
||||
];
|
||||
|
||||
// if there is no type yet, stop here.
|
||||
if ($this->entity->getType()) {
|
||||
|
||||
$alias_type = $this->entity->getAliasType();
|
||||
|
||||
$form['pattern_container']['pattern'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => 'Path pattern',
|
||||
'#default_value' => $this->entity->getPattern(),
|
||||
'#size' => 65,
|
||||
'#maxlength' => 1280,
|
||||
'#element_validate' => array('token_element_validate', 'pathauto_pattern_validate'),
|
||||
'#after_build' => array('token_element_validate'),
|
||||
'#token_types' => $alias_type->getTokenTypes(),
|
||||
'#min_tokens' => 1,
|
||||
'#required' => TRUE,
|
||||
);
|
||||
|
||||
// Show the token help relevant to this pattern type.
|
||||
$form['pattern_container']['token_help'] = array(
|
||||
'#theme' => 'token_tree_link',
|
||||
'#token_types' => $alias_type->getTokenTypes(),
|
||||
);
|
||||
|
||||
// Expose bundle and language conditions.
|
||||
if ($alias_type->getDerivativeId() && $entity_type = $this->entityTypeManager->getDefinition($alias_type->getDerivativeId())) {
|
||||
|
||||
$default_bundles = [];
|
||||
$default_languages = [];
|
||||
foreach ($this->entity->getSelectionConditions() as $condition_id => $condition) {
|
||||
if (in_array($condition->getPluginId(), ['entity_bundle:' . $entity_type->id(), 'node_type'])) {
|
||||
$default_bundles = $condition->getConfiguration()['bundles'];
|
||||
}
|
||||
elseif ($condition->getPluginId() == 'language') {
|
||||
$default_languages = $condition->getConfiguration()['langcodes'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity_type->hasKey('bundle') && $bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type->id())) {
|
||||
$bundle_options = [];
|
||||
foreach ($bundles as $id => $info) {
|
||||
$bundle_options[$id] = $info['label'];
|
||||
}
|
||||
$form['pattern_container']['bundles'] = array(
|
||||
'#title' => $entity_type->getBundleLabel(),
|
||||
'#type' => 'checkboxes',
|
||||
'#options' => $bundle_options,
|
||||
'#default_value' => $default_bundles,
|
||||
'#description' => $this->t('Check to which types this pattern should be applied. Leave empty to allow any.'),
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->languageManager->isMultilingual() && $entity_type->isTranslatable()) {
|
||||
$language_options = [];
|
||||
foreach ($this->languageManager->getLanguages() as $id => $language) {
|
||||
$language_options[$id] = $language->getName();
|
||||
}
|
||||
$form['pattern_container']['languages'] = array(
|
||||
'#title' => $this->t('Languages'),
|
||||
'#type' => 'checkboxes',
|
||||
'#options' => $language_options,
|
||||
'#default_value' => $default_languages,
|
||||
'#description' => $this->t('Check to which languages this pattern should be applied. Leave empty to allow any.'),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$form['label'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Label'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $this->entity->label(),
|
||||
'#required' => TRUE,
|
||||
'#description' => $this->t('A short name to help you identify this pattern in the patterns list.'),
|
||||
);
|
||||
|
||||
$form['id'] = array(
|
||||
'#type' => 'machine_name',
|
||||
'#title' => $this->t('ID'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $this->entity->id(),
|
||||
'#required' => TRUE,
|
||||
'#disabled' => !$this->entity->isNew(),
|
||||
'#machine_name' => array(
|
||||
'exists' => 'Drupal\pathauto\Entity\PathautoPattern::load',
|
||||
),
|
||||
);
|
||||
|
||||
$form['status'] = [
|
||||
'#title' => $this->t('Enabled'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $this->entity->status(),
|
||||
];
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function buildEntity(array $form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\pathauto\PathautoPatternInterface $entity */
|
||||
$entity = parent::buildEntity($form, $form_state);
|
||||
|
||||
// Will only be used for new patterns.
|
||||
$default_weight = 0;
|
||||
|
||||
$alias_type = $entity->getAliasType();
|
||||
if ($alias_type->getDerivativeId() && $this->entityTypeManager->hasDefinition($alias_type->getDerivativeId())) {
|
||||
$entity_type = $alias_type->getDerivativeId();
|
||||
// First, remove bundle and language conditions.
|
||||
foreach ($entity->getSelectionConditions() as $condition_id => $condition) {
|
||||
if (in_array($condition->getPluginId(), ['entity_bundle:' . $entity_type, 'node_type', 'language'])) {
|
||||
$entity->removeSelectionCondition($condition_id);
|
||||
}
|
||||
}
|
||||
|
||||
if ($bundles = array_filter((array) $form_state->getValue('bundles'))) {
|
||||
$default_weight -= 5;
|
||||
$plugin_id = $entity_type == 'node' ? 'node_type' : 'entity_bundle:' . $entity_type;
|
||||
$entity->addSelectionCondition(
|
||||
[
|
||||
'id' => $plugin_id,
|
||||
'bundles' => $bundles,
|
||||
'negate' => FALSE,
|
||||
'context_mapping' => [
|
||||
$entity_type => $entity_type,
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ($languages = array_filter((array) $form_state->getValue('languages'))) {
|
||||
$default_weight -= 5;
|
||||
$language_mapping = $entity_type . ':' . $this->entityTypeManager->getDefinition($entity_type)->getKey('langcode') . ':language';
|
||||
$entity->addSelectionCondition(
|
||||
[
|
||||
'id' => 'language',
|
||||
'langcodes' => array_combine($languages, $languages),
|
||||
'negate' => FALSE,
|
||||
'context_mapping' => [
|
||||
'language' => $language_mapping,
|
||||
]
|
||||
]
|
||||
);
|
||||
$entity->addRelationship($language_mapping, t('Language'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($entity->isNew()) {
|
||||
$entity->setWeight($default_weight);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
parent::save($form, $form_state);
|
||||
drupal_set_message($this->t('Pattern @label saved.', ['@label' => $this->entity->label()]));
|
||||
$form_state->setRedirectUrl($this->entity->toUrl('collection'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles switching the type selector.
|
||||
*/
|
||||
public function ajaxReplacePatternForm($form, FormStateInterface $form_state) {
|
||||
return $form['pattern_container'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles submit call when alias type is selected.
|
||||
*/
|
||||
public function submitSelectType(array $form, FormStateInterface $form_state) {
|
||||
$this->entity = $this->buildEntity($form, $form_state);
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides the pathauto pattern disable disable form.
|
||||
*/
|
||||
class PatternEnableForm extends EntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to enable the pattern %label?', array('%label' => $this->entity->label()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.pathauto_pattern.collection');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Enable');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->entity->enable()->save();
|
||||
drupal_set_message($this->t('Enabled pattern %label.', array('%label' => $this->entity->label())));
|
||||
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
/**
|
||||
* Provides an interface for Messengers.
|
||||
*/
|
||||
interface MessengerInterface {
|
||||
|
||||
/**
|
||||
* Adds a message.
|
||||
*
|
||||
* @param string $message
|
||||
* The message to add.
|
||||
* @param string $op
|
||||
* (optional) The operation being performed.
|
||||
*/
|
||||
public function addMessage($message, $op = NULL);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\path\Plugin\Field\FieldType\PathFieldItemList;
|
||||
|
||||
class PathautoFieldItemList extends PathFieldItemList {
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function delegateMethod($method) {
|
||||
// @todo Workaround until this is fixed, see
|
||||
// https://www.drupal.org/project/drupal/issues/2946289.
|
||||
$this->ensureComputedValue();
|
||||
|
||||
// Duplicate the logic instead of calling the parent due to the dynamic
|
||||
// arguments.
|
||||
$result = [];
|
||||
$args = array_slice(func_get_args(), 1);
|
||||
foreach ($this->list as $delta => $item) {
|
||||
// call_user_func_array() is way slower than a direct call so we avoid
|
||||
// using it if have no parameters.
|
||||
$result[$delta] = $args ? call_user_func_array([$item, $method], $args) : $item->{$method}();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function computeValue() {
|
||||
parent::computeValue();
|
||||
|
||||
// For a new entity, default to creating a new alias.
|
||||
if ($this->getEntity()->isNew()) {
|
||||
$this->list[0]->set('pathauto', PathautoState::CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function delete() {
|
||||
\Drupal::service('pathauto.alias_storage_helper')->deleteEntityPathAll($this->getEntity());
|
||||
$this->first()->get('pathauto')->purge();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\RevisionableInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Drupal\Core\Utility\Token;
|
||||
use Drupal\token\TokenEntityMapperInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
|
||||
/**
|
||||
* Provides methods for generating path aliases.
|
||||
*/
|
||||
class PathautoGenerator implements PathautoGeneratorInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* Module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Token service.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\Token
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
/**
|
||||
* Calculated pattern for a specific entity.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $patterns = array();
|
||||
|
||||
/**
|
||||
* Available patterns per entity type ID.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $patternsByEntityType = array();
|
||||
|
||||
/**
|
||||
* The alias cleaner.
|
||||
*
|
||||
* @var \Drupal\pathauto\AliasCleanerInterface
|
||||
*/
|
||||
protected $aliasCleaner;
|
||||
|
||||
/**
|
||||
* The alias storage helper.
|
||||
*
|
||||
* @var \Drupal\pathauto\AliasStorageHelperInterface
|
||||
*/
|
||||
protected $aliasStorageHelper;
|
||||
|
||||
/**
|
||||
* The alias uniquifier.
|
||||
*
|
||||
* @var \Drupal\pathauto\AliasUniquifierInterface
|
||||
*/
|
||||
protected $aliasUniquifier;
|
||||
|
||||
/**
|
||||
* The messenger service.
|
||||
*
|
||||
* @var \Drupal\pathauto\MessengerInterface
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* @var \Drupal\token\TokenEntityMapperInterface
|
||||
*/
|
||||
protected $tokenEntityMapper;
|
||||
|
||||
/**
|
||||
* @var Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Creates a new Pathauto manager.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Utility\Token $token
|
||||
* The token utility.
|
||||
* @param \Drupal\pathauto\AliasCleanerInterface $alias_cleaner
|
||||
* The alias cleaner.
|
||||
* @param \Drupal\pathauto\AliasStorageHelperInterface $alias_storage_helper
|
||||
* The alias storage helper.
|
||||
* @param AliasUniquifierInterface $alias_uniquifier
|
||||
* The alias uniquifier.
|
||||
* @param MessengerInterface $messenger
|
||||
* The messenger service.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The string translation service.
|
||||
* @param Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, Token $token, AliasCleanerInterface $alias_cleaner, AliasStorageHelperInterface $alias_storage_helper, AliasUniquifierInterface $alias_uniquifier, MessengerInterface $messenger, TranslationInterface $string_translation, TokenEntityMapperInterface $token_entity_mappper, EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->configFactory = $config_factory;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->token = $token;
|
||||
$this->aliasCleaner = $alias_cleaner;
|
||||
$this->aliasStorageHelper = $alias_storage_helper;
|
||||
$this->aliasUniquifier = $alias_uniquifier;
|
||||
$this->messenger = $messenger;
|
||||
$this->stringTranslation = $string_translation;
|
||||
$this->tokenEntityMapper = $token_entity_mappper;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createEntityAlias(EntityInterface $entity, $op) {
|
||||
// Retrieve and apply the pattern for this content type.
|
||||
$pattern = $this->getPatternByEntity($entity);
|
||||
if (empty($pattern)) {
|
||||
// No pattern? Do nothing (otherwise we may blow away existing aliases...)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$source = '/' . $entity->toUrl()->getInternalPath();
|
||||
$config = $this->configFactory->get('pathauto.settings');
|
||||
$langcode = $entity->language()->getId();
|
||||
|
||||
// Core does not handle aliases with language Not Applicable.
|
||||
if ($langcode == LanguageInterface::LANGCODE_NOT_APPLICABLE) {
|
||||
$langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
|
||||
}
|
||||
|
||||
// Build token data.
|
||||
$data = [
|
||||
$this->tokenEntityMapper->getTokenTypeForEntityType($entity->getEntityTypeId()) => $entity,
|
||||
];
|
||||
|
||||
// Allow other modules to alter the pattern.
|
||||
$context = array(
|
||||
'module' => $entity->getEntityType()->getProvider(),
|
||||
'op' => $op,
|
||||
'source' => $source,
|
||||
'data' => $data,
|
||||
'bundle' => $entity->bundle(),
|
||||
'language' => &$langcode,
|
||||
);
|
||||
// @todo Is still hook still useful?
|
||||
$this->moduleHandler->alter('pathauto_pattern', $pattern, $context);
|
||||
|
||||
// Special handling when updating an item which is already aliased.
|
||||
$existing_alias = NULL;
|
||||
if ($op == 'update' || $op == 'bulkupdate') {
|
||||
if ($existing_alias = $this->aliasStorageHelper->loadBySource($source, $langcode)) {
|
||||
switch ($config->get('update_action')) {
|
||||
case PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW:
|
||||
// If an alias already exists,
|
||||
// and the update action is set to do nothing,
|
||||
// then gosh-darn it, do nothing.
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace any tokens in the pattern.
|
||||
// Uses callback option to clean replacements. No sanitization.
|
||||
// Pass empty BubbleableMetadata object to explicitly ignore cacheablity,
|
||||
// as the result is never rendered.
|
||||
$alias = $this->token->replace($pattern->getPattern(), $data, array(
|
||||
'clear' => TRUE,
|
||||
'callback' => array($this->aliasCleaner, 'cleanTokenValues'),
|
||||
'langcode' => $langcode,
|
||||
'pathauto' => TRUE,
|
||||
), new BubbleableMetadata());
|
||||
|
||||
// Check if the token replacement has not actually replaced any values. If
|
||||
// that is the case, then stop because we should not generate an alias.
|
||||
// @see token_scan()
|
||||
$pattern_tokens_removed = preg_replace('/\[[^\s\]:]*:[^\s\]]*\]/', '', $pattern->getPattern());
|
||||
if ($alias === $pattern_tokens_removed) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$alias = $this->aliasCleaner->cleanAlias($alias);
|
||||
|
||||
// Allow other modules to alter the alias.
|
||||
$context['source'] = &$source;
|
||||
$context['pattern'] = $pattern;
|
||||
$this->moduleHandler->alter('pathauto_alias', $alias, $context);
|
||||
|
||||
// If we have arrived at an empty string, discontinue.
|
||||
if (!Unicode::strlen($alias)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// If the alias already exists, generate a new, hopefully unique, variant.
|
||||
$original_alias = $alias;
|
||||
$this->aliasUniquifier->uniquify($alias, $source, $langcode);
|
||||
if ($original_alias != $alias) {
|
||||
// Alert the user why this happened.
|
||||
$this->messenger->addMessage($this->t('The automatically generated alias %original_alias conflicted with an existing alias. Alias changed to %alias.', array(
|
||||
'%original_alias' => $original_alias,
|
||||
'%alias' => $alias,
|
||||
)), $op);
|
||||
}
|
||||
|
||||
// Return the generated alias if requested.
|
||||
if ($op == 'return') {
|
||||
return $alias;
|
||||
}
|
||||
|
||||
// Build the new path alias array and send it off to be created.
|
||||
$path = array(
|
||||
'source' => $source,
|
||||
'alias' => $alias,
|
||||
'language' => $langcode,
|
||||
);
|
||||
|
||||
return $this->aliasStorageHelper->save($path, $existing_alias, $op);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads pathauto patterns for a given entity type ID
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* An entity type ID.
|
||||
*
|
||||
* @return \Drupal\pathauto\PathautoPatternInterface[]
|
||||
* A list of patterns, sorted by weight.
|
||||
*/
|
||||
protected function getPatternByEntityType($entity_type_id) {
|
||||
if (!isset($this->patternsByEntityType[$entity_type_id])) {
|
||||
$ids = \Drupal::entityQuery('pathauto_pattern')
|
||||
->condition('type', array_keys(\Drupal::service('plugin.manager.alias_type')
|
||||
->getPluginDefinitionByType($this->tokenEntityMapper->getTokenTypeForEntityType($entity_type_id))))
|
||||
->condition('status', 1)
|
||||
->sort('weight')
|
||||
->execute();
|
||||
|
||||
$this->patternsByEntityType[$entity_type_id] = \Drupal::entityTypeManager()
|
||||
->getStorage('pathauto_pattern')
|
||||
->loadMultiple($ids);
|
||||
}
|
||||
|
||||
return $this->patternsByEntityType[$entity_type_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPatternByEntity(EntityInterface $entity) {
|
||||
$langcode = $entity->language()->getId();
|
||||
if (!isset($this->patterns[$entity->getEntityTypeId()][$entity->id()][$langcode])) {
|
||||
foreach ($this->getPatternByEntityType($entity->getEntityTypeId()) as $pattern) {
|
||||
if ($pattern->applies($entity)) {
|
||||
$this->patterns[$entity->getEntityTypeId()][$entity->id()][$langcode] = $pattern;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If still not set.
|
||||
if (!isset($this->patterns[$entity->getEntityTypeId()][$entity->id()][$langcode])) {
|
||||
$this->patterns[$entity->getEntityTypeId()][$entity->id()][$langcode] = NULL;
|
||||
}
|
||||
}
|
||||
return $this->patterns[$entity->getEntityTypeId()][$entity->id()][$langcode];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetCaches() {
|
||||
$this->patterns = [];
|
||||
$this->patternsByEntityType = [];
|
||||
$this->aliasCleaner->resetCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateEntityAlias(EntityInterface $entity, $op, array $options = array()) {
|
||||
// Skip if the entity does not have the path field.
|
||||
if (!($entity instanceof ContentEntityInterface) || !$entity->hasField('path')) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Skip if pathauto processing is disabled.
|
||||
if ($entity->path->pathauto != PathautoState::CREATE && empty($options['force'])) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Only act if this is the default revision.
|
||||
if ($entity instanceof RevisionableInterface && !$entity->isDefaultRevision()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$options += array('language' => $entity->language()->getId());
|
||||
$type = $entity->getEntityTypeId();
|
||||
|
||||
// Skip processing if the entity has no pattern.
|
||||
if (!$this->getPatternByEntity($entity)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Deal with taxonomy specific logic.
|
||||
// @todo Update and test forum related code.
|
||||
if ($type == 'taxonomy_term') {
|
||||
|
||||
$config_forum = $this->configFactory->get('forum.settings');
|
||||
if ($entity->getVocabularyId() == $config_forum->get('vocabulary')) {
|
||||
$type = 'forum';
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->createEntityAlias($entity, $op);
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
drupal_set_message($e->getMessage(), 'error');
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// @todo Move this to a method on the pattern plugin.
|
||||
if ($type == 'taxonomy_term') {
|
||||
foreach ($this->loadTermChildren($entity->id()) as $subterm) {
|
||||
$this->updateEntityAlias($subterm, $op, $options);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all children of a term ID.
|
||||
*
|
||||
* @param int $tid
|
||||
* Term ID to retrieve parents for.
|
||||
*
|
||||
* @return \Drupal\taxonomy\TermInterface[]
|
||||
* An array of term objects that are the children of the term $tid.
|
||||
*/
|
||||
protected function loadTermChildren($tid) {
|
||||
return $this->entityTypeManager->getStorage('taxonomy_term')->loadChildren($tid);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Provides and interface for PathautoGenerator.
|
||||
*/
|
||||
interface PathautoGeneratorInterface {
|
||||
|
||||
/**
|
||||
* "Do nothing. Leave the old alias intact."
|
||||
*/
|
||||
const UPDATE_ACTION_NO_NEW = 0;
|
||||
|
||||
/**
|
||||
* "Create a new alias. Leave the existing alias functioning."
|
||||
*/
|
||||
const UPDATE_ACTION_LEAVE = 1;
|
||||
|
||||
/**
|
||||
* "Create a new alias. Delete the old alias."
|
||||
*/
|
||||
const UPDATE_ACTION_DELETE = 2;
|
||||
|
||||
/**
|
||||
* Remove the punctuation from the alias.
|
||||
*/
|
||||
const PUNCTUATION_REMOVE = 0;
|
||||
|
||||
/**
|
||||
* Replace the punctuation with the separator in the alias.
|
||||
*/
|
||||
const PUNCTUATION_REPLACE = 1;
|
||||
|
||||
/**
|
||||
* Leave the punctuation as it is in the alias.
|
||||
*/
|
||||
const PUNCTUATION_DO_NOTHING = 2;
|
||||
|
||||
/**
|
||||
* Resets internal caches.
|
||||
*/
|
||||
public function resetCaches();
|
||||
|
||||
/**
|
||||
* Load an alias pattern entity by entity, bundle, and language.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* An entity.
|
||||
* @return \Drupal\pathauto\PathautoPatternInterface|null
|
||||
*/
|
||||
public function getPatternByEntity(EntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Apply patterns to create an alias.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity.
|
||||
* @param string $op
|
||||
* Operation being performed on the content being aliased
|
||||
* ('insert', 'update', 'return', or 'bulkupdate').
|
||||
*
|
||||
* @return array|string
|
||||
* The alias that was created.
|
||||
*
|
||||
* @see _pathauto_set_alias()
|
||||
*/
|
||||
public function createEntityAlias(EntityInterface $entity, $op);
|
||||
|
||||
/**
|
||||
* Creates or updates an alias for the given entity.
|
||||
*
|
||||
* @param EntityInterface $entity
|
||||
* Entity for which to update the alias.
|
||||
* @param string $op
|
||||
* The operation performed (insert, update)
|
||||
* @param array $options
|
||||
* - force: will force updating the path
|
||||
* - language: the language for which to create the alias
|
||||
*
|
||||
* @return array|null
|
||||
* - An array with alias data in case the alias has been created or updated.
|
||||
* - NULL if no operation performed.
|
||||
*/
|
||||
public function updateEntityAlias(EntityInterface $entity, $op, array $options = array());
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
use Drupal\path\Plugin\Field\FieldType\PathItem;
|
||||
|
||||
/**
|
||||
* Extends the default PathItem implementation to generate aliases.
|
||||
*/
|
||||
class PathautoItem extends PathItem {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
|
||||
$properties = parent::propertyDefinitions($field_definition);
|
||||
$properties['pathauto'] = DataDefinition::create('integer')
|
||||
->setLabel(t('Pathauto state'))
|
||||
->setDescription(t('Whether an automated alias should be created or not.'))
|
||||
->setComputed(TRUE)
|
||||
->setClass('\Drupal\pathauto\PathautoState');
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postSave($update) {
|
||||
// Only allow the parent implementation to act if pathauto will not create
|
||||
// an alias.
|
||||
if ($this->pathauto == PathautoState::SKIP) {
|
||||
parent::postSave($update);
|
||||
}
|
||||
else {
|
||||
\Drupal::service('pathauto.generator')->updateEntityAlias($this->getEntity(), $update ? 'update' : 'insert');
|
||||
}
|
||||
$this->get('pathauto')->persist();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEmpty() {
|
||||
// Make sure that the pathauto state flag does not get lost if just that is
|
||||
// changed.
|
||||
return !$this->alias && !$this->get('pathauto')->hasValue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for defining Pathauto pattern entities.
|
||||
*/
|
||||
interface PathautoPatternInterface extends ConfigEntityInterface {
|
||||
|
||||
/**
|
||||
* Get the tokenized pattern used during alias generation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPattern();
|
||||
|
||||
/**
|
||||
* Set the tokenized pattern to use during alias generation.
|
||||
*
|
||||
* @param string $pattern
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPattern($pattern);
|
||||
|
||||
/**
|
||||
* Gets the type of this pattern.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType();
|
||||
|
||||
/**
|
||||
* @return \Drupal\pathauto\AliasTypeInterface
|
||||
*/
|
||||
public function getAliasType();
|
||||
|
||||
/**
|
||||
* Gets the weight of this pattern (compared to other patterns of this type).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getWeight();
|
||||
|
||||
/**
|
||||
* Sets the weight of this pattern (compared to other patterns of this type).
|
||||
*
|
||||
* @param int $weight
|
||||
* The weight of the variant.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setWeight($weight);
|
||||
|
||||
/**
|
||||
* Returns the contexts of this pattern.
|
||||
*
|
||||
* @return \Drupal\Core\Plugin\Context\ContextInterface[]
|
||||
*/
|
||||
public function getContexts();
|
||||
|
||||
/**
|
||||
* Returns whether a relationship exists.
|
||||
*
|
||||
* @param string $token
|
||||
* Relationship identifier.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the relationship exists, FALSE otherwise.
|
||||
*/
|
||||
public function hasRelationship($token);
|
||||
|
||||
/**
|
||||
* Adds a relationship.
|
||||
*
|
||||
* The relationship will not be changed if it already exists.
|
||||
*
|
||||
* @param string $token
|
||||
* Relationship identifier.
|
||||
* @param string|null $label
|
||||
* (optional) A label, will use the label of the referenced context if not
|
||||
* provided.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addRelationship($token, $label = NULL);
|
||||
|
||||
/**
|
||||
* Replaces a relationship.
|
||||
*
|
||||
* Only already existing relationships are updated.
|
||||
*
|
||||
* @param string $token
|
||||
* Relationship identifier.
|
||||
* @param string|null $label
|
||||
* (optional) A label, will use the label of the referenced context if not
|
||||
* provided.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function replaceRelationship($token, $label);
|
||||
|
||||
/**
|
||||
* Removes a relationship.
|
||||
*
|
||||
* @param string $token
|
||||
* Relationship identifier.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeRelationship($token);
|
||||
|
||||
/**
|
||||
* Returns a list of relationships.
|
||||
*
|
||||
* @return array[]
|
||||
* Keys are context tokens, and values are arrays with the following keys:
|
||||
* - label (string|null, optional): The human-readable label of this
|
||||
* relationship.
|
||||
*/
|
||||
public function getRelationships();
|
||||
|
||||
/**
|
||||
* Gets the selection condition collection.
|
||||
*
|
||||
* @return \Drupal\Core\Condition\ConditionInterface[]|\Drupal\Core\Condition\ConditionPluginCollection
|
||||
*/
|
||||
public function getSelectionConditions();
|
||||
|
||||
/**
|
||||
* Adds selection criteria.
|
||||
*
|
||||
* @param array $configuration
|
||||
* Configuration of the selection criteria.
|
||||
*
|
||||
* @return string
|
||||
* The condition id of the new criteria.
|
||||
*/
|
||||
public function addSelectionCondition(array $configuration);
|
||||
|
||||
/**
|
||||
* Gets selection criteria by condition id.
|
||||
*
|
||||
* @param string $condition_id
|
||||
* The id of the condition.
|
||||
*
|
||||
* @return \Drupal\Core\Condition\ConditionInterface
|
||||
*/
|
||||
public function getSelectionCondition($condition_id);
|
||||
|
||||
/**
|
||||
* Removes selection criteria by condition id.
|
||||
*
|
||||
* @param string $condition_id
|
||||
* The id of the condition.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeSelectionCondition($condition_id);
|
||||
|
||||
/**
|
||||
* Gets the selection logic used by the criteria (ie. "and" or "or").
|
||||
*
|
||||
* @return string
|
||||
* Either "and" or "or"; represents how the selection criteria are combined.
|
||||
*/
|
||||
public function getSelectionLogic();
|
||||
|
||||
/**
|
||||
* Determines if this pattern can apply a given object.
|
||||
*
|
||||
* @param $object
|
||||
* The object used to determine if this plugin can apply.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function applies($object);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\Core\Config\Entity\DraggableListBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Provides a listing of Pathauto pattern entities.
|
||||
*/
|
||||
class PathautoPatternListBuilder extends DraggableListBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $limit = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'pathauto_pattern_list';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['label'] = $this->t('Label');
|
||||
$header['pattern'] = $this->t('Pattern');
|
||||
$header['type'] = $this->t('Pattern type');
|
||||
$header['conditions'] = $this->t('Conditions');
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
/* @var \Drupal\pathauto\PathautoPatternInterface $entity */
|
||||
$row['label'] = $entity->label();
|
||||
$row['patern']['#markup'] = $entity->getPattern();
|
||||
$row['type']['#markup'] = $entity->getAliasType()->getLabel();
|
||||
$row['conditions']['#theme'] = 'item_list';
|
||||
foreach ($entity->getSelectionConditions() as $condition) {
|
||||
$row['conditions']['#items'][] = $condition->summary();
|
||||
}
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\TypedData\TypedData;
|
||||
|
||||
/**
|
||||
* A property that stores in keyvalue whether an entity should receive an alias.
|
||||
*/
|
||||
class PathautoState extends TypedData {
|
||||
|
||||
/**
|
||||
* An automatic alias should not be created.
|
||||
*/
|
||||
const SKIP = 0;
|
||||
|
||||
/**
|
||||
* An automatic alias should be created.
|
||||
*/
|
||||
const CREATE = 1;
|
||||
|
||||
/**
|
||||
* Pathauto state.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Field\FieldItemInterface
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValue() {
|
||||
if ($this->value === NULL) {
|
||||
// If no value has been set or loaded yet, try to load a value if this
|
||||
// entity has already been saved.
|
||||
$this->value = \Drupal::keyValue($this->getCollection())
|
||||
->get(static::getPathautoStateKey($this->parent->getEntity()->id()));
|
||||
// If it was not yet saved or no value was found, then set the flag to
|
||||
// create the alias if there is a matching pattern.
|
||||
if ($this->value === NULL) {
|
||||
$entity = $this->parent->getEntity();
|
||||
$pattern = \Drupal::service('pathauto.generator')->getPatternByEntity($entity);
|
||||
$this->value = !empty($pattern) ? static::CREATE : static::SKIP;
|
||||
}
|
||||
}
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setValue($value, $notify = TRUE) {
|
||||
$this->value = $value;
|
||||
// Notify the parent of any changes.
|
||||
if ($notify && isset($this->parent)) {
|
||||
$this->parent->onChange($this->name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if a value was set.
|
||||
*/
|
||||
public function hasValue() {
|
||||
return $this->value !== NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists the state.
|
||||
*/
|
||||
public function persist() {
|
||||
\Drupal::keyValue($this->getCollection())
|
||||
->set(static::getPathautoStateKey($this->parent->getEntity()->id()), $this->getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the stored state.
|
||||
*/
|
||||
public function purge() {
|
||||
\Drupal::keyValue($this->getCollection())
|
||||
->delete(static::getPathautoStateKey($this->parent->getEntity()->id()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key value collection that should be used for the given entity.
|
||||
* @return string
|
||||
*/
|
||||
protected function getCollection() {
|
||||
return 'pathauto_state.' . $this->parent->getEntity()->getEntityTypeId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the URL aliases for multiple entities of the same type.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID of entities being deleted.
|
||||
* @param int[] $pids_by_id
|
||||
* A list of path IDs keyed by entity ID.
|
||||
*/
|
||||
public static function bulkDelete($entity_type_id, array $pids_by_id) {
|
||||
foreach ($pids_by_id as $id => $pid) {
|
||||
// Some key-values store entries have computed keys.
|
||||
$key = static::getPathautoStateKey($id);
|
||||
if ($key !== $id) {
|
||||
$pids_by_id[$key] = $pid;
|
||||
unset($pids_by_id[$id]);
|
||||
}
|
||||
}
|
||||
$states = \Drupal::keyValue("pathauto_state.$entity_type_id")
|
||||
->getMultiple(array_keys($pids_by_id));
|
||||
|
||||
$pids = [];
|
||||
foreach ($pids_by_id as $id => $pid) {
|
||||
// Only delete aliases that were created by this module.
|
||||
if (isset($states[$id]) && $states[$id] == PathautoState::CREATE) {
|
||||
$pids[] = $pid;
|
||||
}
|
||||
}
|
||||
\Drupal::service('pathauto.alias_storage_helper')->deleteMultiple($pids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the key-value store entry key for 'pathauto_state.*' collections.
|
||||
*
|
||||
* Normally we want to use the entity ID as key for 'pathauto_state.*'
|
||||
* collection entries. But some entity types may use string IDs. When such IDs
|
||||
* are exceeding 128 characters, which is the limit for the 'name' column in
|
||||
* the {key_value} table, the insertion of the ID in {key_value} will fail.
|
||||
* Thus we test if we can use the plain ID or we need to store a hashed
|
||||
* version of the entity ID. Also, it is not possible to rely on the UUID as
|
||||
* entity types might not have one or might use a non-standard format.
|
||||
*
|
||||
* The code is inspired by
|
||||
* \Drupal\Core\Cache\DatabaseBackend::normalizeCid().
|
||||
*
|
||||
* @param int|string $entity_id
|
||||
* The entity id for which to compute the key.
|
||||
*
|
||||
* @return int|string
|
||||
* The key used to store the value in the key-value store.
|
||||
*
|
||||
* @see \Drupal\Core\Cache\DatabaseBackend::normalizeCid()
|
||||
*/
|
||||
public static function getPathautoStateKey($entity_id) {
|
||||
$entity_id_is_ascii = mb_check_encoding($entity_id, 'ASCII');
|
||||
if ($entity_id_is_ascii && strlen($entity_id) <= 128) {
|
||||
// The original entity ID, if it's an ASCII of 128 characters or less.
|
||||
return $entity_id;
|
||||
}
|
||||
return Crypt::hashBase64($entity_id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\path\Plugin\Field\FieldWidget\PathWidget;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Extends the core path widget.
|
||||
*/
|
||||
class PathautoWidget extends PathWidget {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
|
||||
$element = parent::formElement($items, $delta, $element, $form, $form_state);
|
||||
$entity = $items->getEntity();
|
||||
|
||||
// Taxonomy terms do not have an actual fieldset for path settings.
|
||||
// Merge in the defaults.
|
||||
// @todo Impossible to do this in widget, use another solution
|
||||
/*
|
||||
$form['path'] += array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('URL path settings'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => empty($form['path']['alias']),
|
||||
'#group' => 'additional_settings',
|
||||
'#attributes' => array(
|
||||
'class' => array('path-form'),
|
||||
),
|
||||
'#access' => \Drupal::currentUser()->hasPermission('create url aliases') || \Drupal::currentUser()->hasPermission('administer url aliases'),
|
||||
'#weight' => 30,
|
||||
'#tree' => TRUE,
|
||||
'#element_validate' => array('path_form_element_validate'),
|
||||
);*/
|
||||
|
||||
$pattern = \Drupal::service('pathauto.generator')->getPatternByEntity($entity);
|
||||
if (empty($pattern)) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
if (\Drupal::currentUser()->hasPermission('administer pathauto')) {
|
||||
$description = $this->t('Uncheck this to create a custom alias below. <a href="@admin_link">Configure URL alias patterns.</a>', ['@admin_link' => Url::fromRoute('entity.pathauto_pattern.collection')->toString()]);
|
||||
}
|
||||
else {
|
||||
$description = $this->t('Uncheck this to create a custom alias below.');
|
||||
}
|
||||
|
||||
$element['pathauto'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Generate automatic URL alias'),
|
||||
'#default_value' => $entity->path->pathauto,
|
||||
'#description' => $description,
|
||||
'#weight' => -1,
|
||||
);
|
||||
|
||||
// Add JavaScript that will disable the path textfield when the automatic
|
||||
// alias checkbox is checked.
|
||||
$element['alias']['#states']['disabled']['input[name="path[' . $delta . '][pathauto]"]'] = array('checked' => TRUE);
|
||||
|
||||
// Override path.module's vertical tabs summary.
|
||||
$element['alias']['#attached']['library'] = ['pathauto/widget'];
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Action\ActionBase;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\pathauto\PathautoState;
|
||||
|
||||
/**
|
||||
* Pathauto entity update action.
|
||||
*
|
||||
* @Action(
|
||||
* id = "pathauto_update_alias",
|
||||
* label = @Translation("Update URL alias of an entity"),
|
||||
* )
|
||||
*/
|
||||
class UpdateAction extends ActionBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
$entity->path->pathauto = PathautoState::CREATE;
|
||||
\Drupal::service('pathauto.generator')->updateEntityAlias($entity, 'bulkupdate', array('message' => TRUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$result = AccessResult::allowedIfHasPermission($account, 'create url aliases');
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Plugin\Deriver;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Plugin\Context\ContextDefinition;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Drupal\token\TokenEntityMapperInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Deriver that exposes content entities as alias type plugins.
|
||||
*/
|
||||
class EntityAliasTypeDeriver extends DeriverBase implements ContainerDeriverInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\token\TokenEntityMapperInterface
|
||||
*/
|
||||
protected $tokenEntityMapper;
|
||||
|
||||
/**
|
||||
* Constructs new EntityAliasTypeDeriver.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
|
||||
* The entity field manager.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The string translation service.
|
||||
* @apram \Drupal\Token\TokenEntityMapperInterface $token_entity_mapper
|
||||
* The token entity mapper.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, TranslationInterface $string_translation, TokenEntityMapperInterface $token_entity_mapper) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
$this->stringTranslation = $string_translation;
|
||||
$this->tokenEntityMapper = $token_entity_mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_field.manager'),
|
||||
$container->get('string_translation'),
|
||||
$container->get('token.entity_mapper')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
|
||||
// An entity type must have a canonical link template and support fields.
|
||||
if ($entity_type->hasLinkTemplate('canonical') && is_subclass_of($entity_type->getClass(), FieldableEntityInterface::class)) {
|
||||
$base_fields = $this->entityFieldManager->getBaseFieldDefinitions($entity_type_id);
|
||||
if (!isset($base_fields['path'])) {
|
||||
// The entity type does not have a path field and is therefore not
|
||||
// supported.
|
||||
continue;
|
||||
}
|
||||
$this->derivatives[$entity_type_id] = $base_plugin_definition;
|
||||
$this->derivatives[$entity_type_id]['label'] = $entity_type->getLabel();
|
||||
$this->derivatives[$entity_type_id]['types'] = [$this->tokenEntityMapper->getTokenTypeForEntityType($entity_type_id)];
|
||||
$this->derivatives[$entity_type_id]['provider'] = $entity_type->getProvider();
|
||||
$this->derivatives[$entity_type_id]['context'] = [
|
||||
$entity_type_id => new ContextDefinition("entity:$entity_type_id", $this->t('@label being aliased', ['@label' => $entity_type->getLabel()]))
|
||||
];
|
||||
}
|
||||
}
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Plugin\pathauto\AliasType;
|
||||
|
||||
/**
|
||||
* Defines a fallback plugin for missing block plugins.
|
||||
*
|
||||
* @AliasType(
|
||||
* id = "broken",
|
||||
* label = @Translation("Broken"),
|
||||
* admin_label = @Translation("Broken/Missing"),
|
||||
* category = @Translation("AliasType"),
|
||||
* )
|
||||
*/
|
||||
class Broken extends EntityAliasTypeBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLabel() {
|
||||
return $this->t('Broken type');
|
||||
}
|
||||
|
||||
}
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Plugin\pathauto\AliasType;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\Context\Context;
|
||||
use Drupal\Core\Plugin\ContextAwarePluginBase;
|
||||
use Drupal\pathauto\AliasTypeBatchUpdateInterface;
|
||||
use Drupal\pathauto\AliasTypeInterface;
|
||||
use Drupal\pathauto\PathautoState;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* A pathauto alias type plugin for entities with canonical links.
|
||||
*
|
||||
* @AliasType(
|
||||
* id = "canonical_entities",
|
||||
* deriver = "\Drupal\pathauto\Plugin\Deriver\EntityAliasTypeDeriver"
|
||||
* )
|
||||
*/
|
||||
class EntityAliasTypeBase extends ContextAwarePluginBase implements AliasTypeInterface, AliasTypeBatchUpdateInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The language manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The entity manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The key/value manager service.
|
||||
*
|
||||
* @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
|
||||
*/
|
||||
protected $keyValue;
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The path prefix for this entity type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix;
|
||||
|
||||
/**
|
||||
* Constructs a EntityAliasTypeBase instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler service.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity manager service.
|
||||
* @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value
|
||||
* The key/value manager service.
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database connection.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager, EntityTypeManagerInterface $entity_type_manager, KeyValueFactoryInterface $key_value, Connection $database) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->keyValue = $key_value;
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('module_handler'),
|
||||
$container->get('language_manager'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('keyvalue'),
|
||||
$container->get('database')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLabel() {
|
||||
$definition = $this->getPluginDefinition();
|
||||
// Cast the admin label to a string since it is an object.
|
||||
// @see \Drupal\Core\StringTranslation\TranslationWrapper
|
||||
return (string) $definition['label'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTokenTypes() {
|
||||
$definition = $this->getPluginDefinition();
|
||||
return $definition['types'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function batchUpdate($action, &$context) {
|
||||
if (!isset($context['sandbox']['current'])) {
|
||||
$context['sandbox']['count'] = 0;
|
||||
$context['sandbox']['current'] = 0;
|
||||
}
|
||||
|
||||
$entity_type = $this->entityTypeManager->getDefinition($this->getEntityTypeId());
|
||||
$id_key = $entity_type->getKey('id');
|
||||
|
||||
$query = $this->database->select($entity_type->get('base_table'), 'base_table');
|
||||
$query->leftJoin('url_alias', 'ua', "CONCAT('" . $this->getSourcePrefix() . "' , base_table.$id_key) = ua.source");
|
||||
$query->addField('base_table', $id_key, 'id');
|
||||
|
||||
switch ($action) {
|
||||
case 'create':
|
||||
$query->isNull('ua.source');
|
||||
break;
|
||||
case 'update':
|
||||
$query->isNotNull('ua.source');
|
||||
break;
|
||||
case 'all':
|
||||
// Nothing to do. We want all paths.
|
||||
break;
|
||||
default:
|
||||
// Unknown action. Abort!
|
||||
return;
|
||||
}
|
||||
$query->condition('base_table.' . $id_key, $context['sandbox']['current'], '>');
|
||||
$query->orderBy('base_table.' . $id_key);
|
||||
$query->addTag('pathauto_bulk_update');
|
||||
$query->addMetaData('entity', $this->getEntityTypeId());
|
||||
|
||||
// Get the total amount of items to process.
|
||||
if (!isset($context['sandbox']['total'])) {
|
||||
$context['sandbox']['total'] = $query->countQuery()->execute()->fetchField();
|
||||
|
||||
// If there are no entities to update, then stop immediately.
|
||||
if (!$context['sandbox']['total']) {
|
||||
$context['finished'] = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$query->range(0, 25);
|
||||
$ids = $query->execute()->fetchCol();
|
||||
|
||||
$updates = $this->bulkUpdate($ids);
|
||||
$context['sandbox']['count'] += count($ids);
|
||||
$context['sandbox']['current'] = max($ids);
|
||||
$context['results']['updates'] += $updates;
|
||||
$context['message'] = $this->t('Updated alias for %label @id.', array('%label' => $entity_type->getLabel(), '@id' => end($ids)));
|
||||
|
||||
if ($context['sandbox']['count'] != $context['sandbox']['total']) {
|
||||
$context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function batchDelete(&$context) {
|
||||
if (!isset($context['sandbox']['current'])) {
|
||||
$context['sandbox']['count'] = 0;
|
||||
$context['sandbox']['current'] = 0;
|
||||
}
|
||||
|
||||
$entity_type = $this->entityTypeManager->getDefinition($this->getEntityTypeId());
|
||||
$id_key = $entity_type->getKey('id');
|
||||
|
||||
$query = $this->database->select($entity_type->get('base_table'), 'base_table');
|
||||
$query->innerJoin('url_alias', 'ua', "CONCAT('" . $this->getSourcePrefix() . "' , base_table.$id_key) = ua.source");
|
||||
$query->addField('base_table', $id_key, 'id');
|
||||
$query->addField('ua', 'pid');
|
||||
$query->condition('ua.pid', $context['sandbox']['current'], '>');
|
||||
$query->orderBy('ua.pid');
|
||||
$query->addTag('pathauto_bulk_delete');
|
||||
$query->addMetaData('entity', $this->getEntityTypeId());
|
||||
|
||||
// Get the total amount of items to process.
|
||||
if (!isset($context['sandbox']['total'])) {
|
||||
$context['sandbox']['total'] = $query->countQuery()->execute()->fetchField();
|
||||
|
||||
// If there are no entities to delete, then stop immediately.
|
||||
if (!$context['sandbox']['total']) {
|
||||
$context['finished'] = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$query->range(0, 100);
|
||||
$pids_by_id = $query->execute()->fetchAllKeyed();
|
||||
|
||||
PathautoState::bulkDelete($this->getEntityTypeId(), $pids_by_id);
|
||||
$context['sandbox']['count'] += count($pids_by_id);
|
||||
$context['sandbox']['current'] = max($pids_by_id);
|
||||
$context['results']['deletions'][] = $this->getLabel();
|
||||
|
||||
if ($context['sandbox']['count'] != $context['sandbox']['total']) {
|
||||
$context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entity type ID.
|
||||
*
|
||||
* @return string
|
||||
* The entity type ID.
|
||||
*/
|
||||
protected function getEntityTypeId() {
|
||||
return $this->getDerivativeId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the URL aliases for multiple entities.
|
||||
*
|
||||
* @param array $ids
|
||||
* An array of entity IDs.
|
||||
* @param array $options
|
||||
* An optional array of additional options.
|
||||
*
|
||||
* @return int
|
||||
* The number of updated URL aliases.
|
||||
*/
|
||||
protected function bulkUpdate(array $ids, array $options = array()) {
|
||||
$options += array('message' => FALSE);
|
||||
$updates = 0;
|
||||
|
||||
$entities = $this->entityTypeManager->getStorage($this->getEntityTypeId())->loadMultiple($ids);
|
||||
foreach ($entities as $entity) {
|
||||
// Update aliases for the entity's default language and its translations.
|
||||
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
|
||||
$translated_entity = $entity->getTranslation($langcode);
|
||||
$result = \Drupal::service('pathauto.generator')->updateEntityAlias($translated_entity, 'bulkupdate', $options);
|
||||
if ($result) {
|
||||
$updates++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($options['message'])) {
|
||||
drupal_set_message(\Drupal::translation()->formatPlural(count($ids), 'Updated 1 %label URL alias.', 'Updated @count %label URL aliases.'), array('%label' => $this->getLabel()));
|
||||
}
|
||||
|
||||
return $updates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the URL aliases for multiple entities.
|
||||
*
|
||||
* @param int[] $pids_by_id
|
||||
* A list of path IDs keyed by entity ID.
|
||||
*
|
||||
* @deprecated Use \Drupal\pathauto\PathautoState::bulkDelete() instead.
|
||||
*/
|
||||
protected function bulkDelete(array $pids_by_id) {
|
||||
PathautoState::bulkDelete($this->getEntityTypeId(), $pids_by_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$dependencies = [];
|
||||
$dependencies['module'][] = $this->entityTypeManager->getDefinition($this->getEntityTypeId())->getProvider();
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies($object) {
|
||||
return $object instanceof FieldableEntityInterface && $object->getEntityTypeId() == $this->getEntityTypeId();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSourcePrefix() {
|
||||
if (empty($this->prefix)) {
|
||||
$entity_type = $this->entityTypeManager->getDefinition($this->getEntityTypeId());
|
||||
$path = $entity_type->getLinkTemplate('canonical');
|
||||
$this->prefix = substr($path, 0, strpos($path, '{'));
|
||||
}
|
||||
return $this->prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setContextValue($name, $value) {
|
||||
// Overridden to avoid merging existing cacheability metadata, which is not
|
||||
// relevant for alias type plugins.
|
||||
$this->context[$name] = new Context($this->getContextDefinition($name), $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Plugin\pathauto\AliasType;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* A pathauto alias type plugin for forum terms.
|
||||
*
|
||||
* @AliasType(
|
||||
* id = "forum",
|
||||
* label = @Translation("Forum"),
|
||||
* types = {"term"},
|
||||
* provider = "forum",
|
||||
* context = {
|
||||
* "taxonomy_term" = @ContextDefinition("entity:taxonomy_term")
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class ForumAliasType extends EntityAliasTypeBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* Constructs a ForumAliasType instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler service.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity manager service.
|
||||
* @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value
|
||||
* The key/value manager service.
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database service.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager, EntityTypeManagerInterface $entity_type_manager, KeyValueFactoryInterface $key_value, Connection $database, ConfigFactoryInterface $config_factory) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $module_handler, $language_manager, $entity_type_manager, $key_value, $database);
|
||||
$this->configFactory = $config_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('module_handler'),
|
||||
$container->get('language_manager'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('keyvalue'),
|
||||
$container->get('database'),
|
||||
$container->get('config.factory')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntityTypeId() {
|
||||
return 'taxonomy_term';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSourcePrefix() {
|
||||
return '/forum/';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies($object) {
|
||||
if (parent::applies($object)) {
|
||||
/** @var \Drupal\taxonomy\TermInterface $object */
|
||||
$vid = $this->configFactory->get('forum.settings')->get('vocabulary');
|
||||
return $object->getVocabularyId() == $vid;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Tests;
|
||||
|
||||
use Drupal\pathauto\PathautoGeneratorInterface;
|
||||
use Drupal\pathauto\PathautoState;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Bulk update functionality tests.
|
||||
*
|
||||
* @group pathauto
|
||||
*/
|
||||
class PathautoBulkUpdateTest extends WebTestBase {
|
||||
|
||||
use PathautoTestHelperTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node', 'pathauto', 'forum');
|
||||
|
||||
/**
|
||||
* Admin user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* The created nodes.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $nodes;
|
||||
|
||||
/**
|
||||
* The created patterns.
|
||||
*
|
||||
* @var \Drupal\pathauto\PathautoPatternInterface
|
||||
*/
|
||||
protected $patterns;
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Allow other modules to add additional permissions for the admin user.
|
||||
$permissions = array(
|
||||
'administer pathauto',
|
||||
'administer url aliases',
|
||||
'create url aliases',
|
||||
'administer forums',
|
||||
);
|
||||
$this->adminUser = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
$this->patterns = array();
|
||||
$this->patterns['node'] = $this->createPattern('node', '/content/[node:title]');
|
||||
$this->patterns['user'] = $this->createPattern('user', '/users/[user:name]');
|
||||
$this->patterns['forum'] = $this->createPattern('forum', '/forums/[term:name]');
|
||||
}
|
||||
|
||||
function testBulkUpdate() {
|
||||
// Create some nodes.
|
||||
$this->nodes = array();
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$node = $this->drupalCreateNode();
|
||||
$this->nodes[$node->id()] = $node;
|
||||
}
|
||||
|
||||
// Clear out all aliases.
|
||||
$this->deleteAllAliases();
|
||||
|
||||
// Bulk create aliases.
|
||||
$edit = array(
|
||||
'update[canonical_entities:node]' => TRUE,
|
||||
'update[canonical_entities:user]' => TRUE,
|
||||
'update[forum]' => TRUE,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/search/path/update_bulk', $edit, t('Update'));
|
||||
|
||||
// This has generated 8 aliases: 5 nodes, 2 users and 1 forum.
|
||||
$this->assertText('Generated 8 URL aliases.');
|
||||
|
||||
// Check that aliases have actually been created.
|
||||
foreach ($this->nodes as $node) {
|
||||
$this->assertEntityAliasExists($node);
|
||||
}
|
||||
$this->assertEntityAliasExists($this->adminUser);
|
||||
// This is the default "General discussion" forum.
|
||||
$this->assertAliasExists(['source' => '/taxonomy/term/1']);
|
||||
|
||||
// Add a new node.
|
||||
$new_node = $this->drupalCreateNode(array('path' => array('alias' => '', 'pathauto' => PathautoState::SKIP)));
|
||||
|
||||
// Run the update again which should not run against any nodes.
|
||||
$this->drupalPostForm('admin/config/search/path/update_bulk', $edit, t('Update'));
|
||||
$this->assertText('No new URL aliases to generate.');
|
||||
$this->assertNoEntityAliasExists($new_node);
|
||||
|
||||
// Make sure existing aliases can be overriden.
|
||||
$this->drupalPostForm('admin/config/search/path/settings', ['update_action' => PathautoGeneratorInterface::UPDATE_ACTION_DELETE], t('Save configuration'));
|
||||
|
||||
// Patterns did not change, so no aliases should be regenerated.
|
||||
$edit['action'] = 'all';
|
||||
$this->drupalPostForm('admin/config/search/path/update_bulk', $edit, t('Update'));
|
||||
$this->assertText('No new URL aliases to generate.');
|
||||
|
||||
// Update the node pattern, and leave other patterns alone. Existing nodes should get a new alias,
|
||||
// except the node above whose alias is manually set. Other aliases must be left alone.
|
||||
$this->patterns['node']->delete();
|
||||
$this->patterns['node'] = $this->createPattern('node', '/archive/node-[node:nid]');
|
||||
|
||||
$this->drupalPostForm('admin/config/search/path/update_bulk', $edit, t('Update'));
|
||||
$this->assertText('Generated 5 URL aliases.');
|
||||
|
||||
// Prevent existing aliases to be overriden. The bulk generate page should only offer
|
||||
// to create an alias for paths which have none.
|
||||
$this->drupalPostForm('admin/config/search/path/settings', ['update_action' => PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW], t('Save configuration'));
|
||||
|
||||
$this->drupalGet('admin/config/search/path/update_bulk');
|
||||
$this->assertFieldByName('action', 'create');
|
||||
$this->assertText('Pathauto settings are set to ignore paths which already have a URL alias.');
|
||||
$this->assertNoFieldByName('action', 'update');
|
||||
$this->assertNoFieldByName('action', 'all');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests alias generation for nodes that existed before installing Pathauto.
|
||||
*/
|
||||
function testBulkUpdateExistingContent() {
|
||||
// Create a node.
|
||||
$node = $this->drupalCreateNode();
|
||||
|
||||
// Delete its alias and Pathauto metadata.
|
||||
\Drupal::service('pathauto.alias_storage_helper')->deleteEntityPathAll($node);
|
||||
$node->path->first()->get('pathauto')->purge();
|
||||
\Drupal::entityTypeManager()->getStorage('node')->resetCache(array($node->id()));
|
||||
|
||||
// Execute bulk generation.
|
||||
// Bulk create aliases.
|
||||
$edit = array(
|
||||
'update[canonical_entities:node]' => TRUE,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/search/path/update_bulk', $edit, t('Update'));
|
||||
|
||||
// Verify that the alias was created for the node.
|
||||
$this->assertText('Generated 1 URL alias.');
|
||||
}
|
||||
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\comment\Tests\CommentTestTrait;
|
||||
|
||||
/**
|
||||
* Tests pathauto settings form.
|
||||
*
|
||||
* @group pathauto
|
||||
*/
|
||||
class PathautoEnablingEntityTypesTest extends WebTestBase {
|
||||
|
||||
use PathautoTestHelperTrait;
|
||||
use CommentTestTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node', 'pathauto', 'comment');
|
||||
|
||||
/**
|
||||
* Admin user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(array('type' => 'article'));
|
||||
$this->addDefaultCommentField('node', 'article');
|
||||
|
||||
$permissions = array(
|
||||
'administer pathauto',
|
||||
'administer url aliases',
|
||||
'create url aliases',
|
||||
'administer nodes',
|
||||
'post comments',
|
||||
);
|
||||
$this->adminUser = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* A suite of tests to verify if the feature to enable and disable the
|
||||
* ability to define alias patterns for a given entity type works. Test with
|
||||
* the comment module, as it is not enabled by default.
|
||||
*/
|
||||
function testEnablingEntityTypes() {
|
||||
// Verify that the comment entity type is not available when trying to add
|
||||
// a new pattern, nor "broken".
|
||||
$this->drupalGet('/admin/config/search/path/patterns/add');
|
||||
$this->assertEqual(count($this->cssSelect('option[value = "canonical_entities:comment"]:contains(Comment)')), 0);
|
||||
$this->assertEqual(count($this->cssSelect('option:contains(Broken)')), 0);
|
||||
|
||||
// Enable the entity type and create a pattern for it.
|
||||
$this->drupalGet('/admin/config/search/path/settings');
|
||||
$edit = [
|
||||
'enabled_entity_types[comment]' => TRUE,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save configuration');
|
||||
$this->createPattern('comment', '/comment/[comment:body]');
|
||||
|
||||
// Create a node, a comment type and a comment entity.
|
||||
$node = $this->drupalCreateNode(['type' => 'article']);
|
||||
$this->drupalGet('/node/' . $node->id());
|
||||
$edit = [
|
||||
'comment_body[0][value]' => 'test-body',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
|
||||
// Verify that an alias has been generated and that the type can no longer
|
||||
// be disabled.
|
||||
$this->assertAliasExists(['alias' => '/comment/test-body']);
|
||||
$this->drupalGet('/admin/config/search/path/settings');
|
||||
$this->assertEqual(count($this->cssSelect('input[name = "enabled_entity_types[comment]"][disabled = "disabled"]')), 1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Tests;
|
||||
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\pathauto\PathautoState;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Test pathauto functionality with localization and translation.
|
||||
*
|
||||
* @group pathauto
|
||||
*/
|
||||
class PathautoLocaleTest extends WebTestBase {
|
||||
|
||||
use PathautoTestHelperTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node', 'pathauto', 'locale', 'content_translation');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create Article node type.
|
||||
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that when an English node is updated, its old English alias is
|
||||
* updated and its newer French alias is left intact.
|
||||
*/
|
||||
function testLanguageAliases() {
|
||||
|
||||
$this->createPattern('node', '/content/[node:title]');
|
||||
|
||||
// Add predefined French language.
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
|
||||
$node = array(
|
||||
'title' => 'English node',
|
||||
'langcode' => 'en',
|
||||
'path' => array(array(
|
||||
'alias' => '/english-node',
|
||||
'pathauto' => FALSE,
|
||||
)),
|
||||
);
|
||||
$node = $this->drupalCreateNode($node);
|
||||
$english_alias = \Drupal::service('path.alias_storage')->load(array('alias' => '/english-node', 'langcode' => 'en'));
|
||||
$this->assertTrue($english_alias, 'Alias created with proper language.');
|
||||
|
||||
// Also save a French alias that should not be left alone, even though
|
||||
// it is the newer alias.
|
||||
$this->saveEntityAlias($node, '/french-node', 'fr');
|
||||
|
||||
// Add an alias with the soon-to-be generated alias, causing the upcoming
|
||||
// alias update to generate a unique alias with the '-0' suffix.
|
||||
$this->saveAlias('/node/invalid', '/content/english-node', Language::LANGCODE_NOT_SPECIFIED);
|
||||
|
||||
// Update the node, triggering a change in the English alias.
|
||||
$node->path->pathauto = PathautoState::CREATE;
|
||||
$node->save();
|
||||
|
||||
// Check that the new English alias replaced the old one.
|
||||
$this->assertEntityAlias($node, '/content/english-node-0', 'en');
|
||||
$this->assertEntityAlias($node, '/french-node', 'fr');
|
||||
$this->assertAliasExists(array('pid' => $english_alias['pid'], 'alias' => '/content/english-node-0'));
|
||||
|
||||
// Create a new node with the same title as before but without
|
||||
// specifying a language.
|
||||
$node = $this->drupalCreateNode(array('title' => 'English node', 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED));
|
||||
|
||||
// Check that the new node had a unique alias generated with the '-0'
|
||||
// suffix.
|
||||
$this->assertEntityAlias($node, '/content/english-node-0', LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that patterns work on multilingual content.
|
||||
*/
|
||||
function testLanguagePatterns() {
|
||||
|
||||
// Allow other modules to add additional permissions for the admin user.
|
||||
$permissions = array(
|
||||
'administer pathauto',
|
||||
'administer url aliases',
|
||||
'create url aliases',
|
||||
'bypass node access',
|
||||
'access content overview',
|
||||
'administer languages',
|
||||
'translate any entity',
|
||||
'administer content translation'
|
||||
|
||||
);
|
||||
$admin_user = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Add French language.
|
||||
$edit = array(
|
||||
'predefined_langcode' => 'fr',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
|
||||
$this->enableArticleTranslation();
|
||||
|
||||
// Create a pattern for English articles.
|
||||
$this->drupalGet('admin/config/search/path/patterns/add');
|
||||
$edit = array(
|
||||
'type' => 'canonical_entities:node',
|
||||
);
|
||||
$this->drupalPostAjaxForm(NULL, $edit, 'type');
|
||||
$edit += array(
|
||||
'pattern' => '/the-articles/[node:title]',
|
||||
'label' => 'English articles',
|
||||
'id' => 'english_articles',
|
||||
'bundles[article]' => TRUE,
|
||||
'languages[en]' => TRUE,
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertText('Pattern English articles saved.');
|
||||
|
||||
// Create a pattern for French articles.
|
||||
$this->drupalGet('admin/config/search/path/patterns/add');
|
||||
$edit = array(
|
||||
'type' => 'canonical_entities:node',
|
||||
);
|
||||
$this->drupalPostAjaxForm(NULL, $edit, 'type');
|
||||
$edit += array(
|
||||
'pattern' => '/les-articles/[node:title]',
|
||||
'label' => 'French articles',
|
||||
'id' => 'french_articles',
|
||||
'bundles[article]' => TRUE,
|
||||
'languages[fr]' => TRUE,
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertText('Pattern French articles saved.');
|
||||
|
||||
// Create a node and its translation. Assert aliases.
|
||||
$edit = array(
|
||||
'title[0][value]' => 'English node',
|
||||
'langcode[0][value]' => 'en',
|
||||
);
|
||||
$this->drupalPostForm('node/add/article', $edit, t('Save'));
|
||||
$english_node = $this->drupalGetNodeByTitle('English node');
|
||||
return;
|
||||
$this->assertAlias('/node/' . $english_node->id(), '/the-articles/english-node', 'en');
|
||||
|
||||
$this->drupalGet('node/' . $english_node->id() . '/translations');
|
||||
$this->clickLink(t('Add'));
|
||||
$edit = array(
|
||||
'title[0][value]' => 'French node',
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
$this->rebuildContainer();
|
||||
$english_node = $this->drupalGetNodeByTitle('English node');
|
||||
$french_node = $english_node->getTranslation('fr');
|
||||
$this->assertAlias('/node/' . $french_node->id(), '/les-articles/french-node', 'fr');
|
||||
|
||||
// Bulk delete and Bulk generate patterns. Assert aliases.
|
||||
$this->deleteAllAliases();
|
||||
// Bulk create aliases.
|
||||
$edit = array(
|
||||
'update[canonical_entities:node]' => TRUE,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/search/path/update_bulk', $edit, t('Update'));
|
||||
$this->assertText(t('Generated 2 URL aliases.'));
|
||||
$this->assertAlias('/node/' . $english_node->id(), '/the-articles/english-node', 'en');
|
||||
$this->assertAlias('/node/' . $french_node->id(), '/les-articles/french-node', 'fr');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the alias created for a node with language Not Applicable.
|
||||
*/
|
||||
public function testLanguageNotApplicable() {
|
||||
$this->drupalLogin($this->rootUser);
|
||||
$this->enableArticleTranslation();
|
||||
|
||||
// Create a pattern for nodes.
|
||||
$pattern = $this->createPattern('node', '/content/[node:title]', -1);
|
||||
$pattern->save();
|
||||
|
||||
// Create a node with language Not Applicable.
|
||||
$node = $this->createNode(['type' => 'article', 'title' => 'Test node', 'langcode' => LanguageInterface::LANGCODE_NOT_APPLICABLE]);
|
||||
|
||||
// Check that the generated alias has language Not Specified.
|
||||
$alias = \Drupal::service('pathauto.alias_storage_helper')->loadBySource('/node/' . $node->id());
|
||||
$this->assertEqual($alias['langcode'], LanguageInterface::LANGCODE_NOT_SPECIFIED, 'PathautoGenerator::createEntityAlias() adjusts the alias langcode from Not Applicable to Not Specified.');
|
||||
|
||||
// Check that the alias works.
|
||||
$this->drupalGet('content/test-node');
|
||||
$this->assertResponse(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables content translation on articles.
|
||||
*/
|
||||
protected function enableArticleTranslation() {
|
||||
// Enable content translation on articles.
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
$edit = array(
|
||||
'entity_types[node]' => TRUE,
|
||||
'settings[node][article][translatable]' => TRUE,
|
||||
'settings[node][article][settings][language][language_alterable]' => TRUE,
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Tests;
|
||||
|
||||
use Drupal\pathauto\PathautoState;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Mass delete functionality tests.
|
||||
*
|
||||
* @group pathauto
|
||||
*/
|
||||
class PathautoMassDeleteTest extends WebTestBase {
|
||||
|
||||
use PathautoTestHelperTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node', 'taxonomy', 'pathauto');
|
||||
|
||||
/**
|
||||
* Admin user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* The test nodes.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $nodes;
|
||||
|
||||
/**
|
||||
* The test accounts.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $accounts;
|
||||
|
||||
/**
|
||||
* The test terms.
|
||||
*
|
||||
* @var \Drupal\taxonomy\TermInterface
|
||||
*/
|
||||
protected $terms;
|
||||
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$permissions = array(
|
||||
'administer pathauto',
|
||||
'administer url aliases',
|
||||
'create url aliases',
|
||||
);
|
||||
$this->adminUser = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
$this->createPattern('node', '/content/[node:title]');
|
||||
$this->createPattern('user', '/users/[user:name]');
|
||||
$this->createPattern('taxonomy_term', '/[term:vocabulary]/[term:name]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the deletion of all the aliases.
|
||||
*/
|
||||
function testDeleteAll() {
|
||||
// 1. Test that deleting all the aliases, of any type, works.
|
||||
$this->generateAliases();
|
||||
$edit = array(
|
||||
'delete[all_aliases]' => TRUE,
|
||||
'options[keep_custom_aliases]' => FALSE,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/search/path/delete_bulk', $edit, t('Delete aliases now!'));
|
||||
$this->assertText(t('All of your path aliases have been deleted.'));
|
||||
$this->assertUrl('admin/config/search/path/delete_bulk');
|
||||
|
||||
// Make sure that all of them are actually deleted.
|
||||
$aliases = \Drupal::database()->select('url_alias', 'ua')->fields('ua', array())->execute()->fetchAll();
|
||||
$this->assertEqual($aliases, array(), "All the aliases have been deleted.");
|
||||
|
||||
// 2. Test deleting only specific (entity type) aliases.
|
||||
$manager = $this->container->get('plugin.manager.alias_type');
|
||||
$pathauto_plugins = array('canonical_entities:node' => 'nodes', 'canonical_entities:taxonomy_term' => 'terms', 'canonical_entities:user' => 'accounts');
|
||||
foreach ($pathauto_plugins as $pathauto_plugin => $attribute) {
|
||||
$this->generateAliases();
|
||||
$edit = array(
|
||||
'delete[plugins][' . $pathauto_plugin . ']' => TRUE,
|
||||
'options[keep_custom_aliases]' => FALSE,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/search/path/delete_bulk', $edit, t('Delete aliases now!'));
|
||||
$alias_type = $manager->createInstance($pathauto_plugin);
|
||||
$this->assertRaw(t('All of your %label path aliases have been deleted.', array('%label' => $alias_type->getLabel())));
|
||||
// Check that the aliases were actually deleted.
|
||||
foreach ($this->{$attribute} as $entity) {
|
||||
$this->assertNoEntityAlias($entity);
|
||||
}
|
||||
|
||||
// Check that the other aliases are not deleted.
|
||||
foreach ($pathauto_plugins as $_pathauto_plugin => $_attribute) {
|
||||
// Skip the aliases that should be deleted.
|
||||
if ($_pathauto_plugin == $pathauto_plugin) {
|
||||
continue;
|
||||
}
|
||||
foreach ($this->{$_attribute} as $entity) {
|
||||
$this->assertEntityAliasExists($entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Test deleting automatically generated aliases only.
|
||||
$this->generateAliases();
|
||||
$edit = array(
|
||||
'delete[all_aliases]' => TRUE,
|
||||
'options[keep_custom_aliases]' => TRUE,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/search/path/delete_bulk', $edit, t('Delete aliases now!'));
|
||||
$this->assertText(t('All of your automatically generated path aliases have been deleted.'));
|
||||
$this->assertUrl('admin/config/search/path/delete_bulk');
|
||||
|
||||
// Make sure that only custom aliases and aliases with no information about
|
||||
// their state still exist.
|
||||
$aliases = \Drupal::database()->select('url_alias', 'ua')->fields('ua', ['source'])->execute()->fetchCol();
|
||||
$this->assertEqual($aliases, ['/node/101', '/node/104', '/node/105'], 'Custom aliases still exist.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to generate aliases.
|
||||
*/
|
||||
function generateAliases() {
|
||||
// Delete all aliases to avoid duplicated aliases. They will be recreated below.
|
||||
$this->deleteAllAliases();
|
||||
|
||||
// We generate a bunch of aliases for nodes, users and taxonomy terms. If
|
||||
// the entities are already created we just update them, otherwise we create
|
||||
// them.
|
||||
if (empty($this->nodes)) {
|
||||
// Create a large number of nodes (100+) to make sure that the batch code works.
|
||||
for ($i = 1; $i <= 105; $i++) {
|
||||
// Set the alias of two nodes manually.
|
||||
$settings = ($i > 103) ? ['path' => ['alias' => "/custom_alias_$i", 'pathauto' => PathautoState::SKIP]] : [];
|
||||
$node = $this->drupalCreateNode($settings);
|
||||
$this->nodes[$node->id()] = $node;
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($this->nodes as $node) {
|
||||
if ($node->id() > 103) {
|
||||
// The alias is set manually.
|
||||
$node->set('path', ['alias' => '/custom_alias_' . $node->id()]);
|
||||
}
|
||||
$node->save();
|
||||
}
|
||||
}
|
||||
// Delete information about the state of an alias to make sure that aliases
|
||||
// with no such data are left alone by default.
|
||||
\Drupal::keyValue('pathauto_state.node')->delete(101);
|
||||
|
||||
if (empty($this->accounts)) {
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$account = $this->drupalCreateUser();
|
||||
$this->accounts[$account->id()] = $account;
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($this->accounts as $id => $account) {
|
||||
$account->save();
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->terms)) {
|
||||
$vocabulary = $this->addVocabulary(array('name' => 'test vocabulary', 'vid' => 'test_vocabulary'));
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$term = $this->addTerm($vocabulary);
|
||||
$this->terms[$term->id()] = $term;
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($this->terms as $term) {
|
||||
$term->save();
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we have aliases for the entities.
|
||||
foreach (array('nodes', 'accounts', 'terms') as $attribute) {
|
||||
foreach ($this->{$attribute} as $entity) {
|
||||
$this->assertEntityAliasExists($entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Tests;
|
||||
use Drupal\pathauto\Entity\PathautoPattern;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\pathauto\PathautoState;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests pathauto node UI integration.
|
||||
*
|
||||
* @group pathauto
|
||||
*/
|
||||
class PathautoNodeWebTest extends WebTestBase {
|
||||
|
||||
use PathautoTestHelperTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node', 'pathauto', 'views', 'taxonomy', 'pathauto_views_test');
|
||||
|
||||
/**
|
||||
* Admin user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
|
||||
$this->drupalCreateContentType(array('type' => 'article'));
|
||||
|
||||
// Allow other modules to add additional permissions for the admin user.
|
||||
$permissions = array(
|
||||
'administer pathauto',
|
||||
'administer url aliases',
|
||||
'create url aliases',
|
||||
'bypass node access',
|
||||
'access content overview',
|
||||
);
|
||||
$this->adminUser = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
$this->createPattern('node', '/content/[node:title]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests editing nodes with different settings.
|
||||
*/
|
||||
function testNodeEditing() {
|
||||
// Ensure that the Pathauto checkbox is checked by default on the node add form.
|
||||
$this->drupalGet('node/add/page');
|
||||
$this->assertFieldChecked('edit-path-0-pathauto');
|
||||
|
||||
// Create a node by saving the node form.
|
||||
$title = ' Testing: node title [';
|
||||
$automatic_alias = '/content/testing-node-title';
|
||||
$this->drupalPostForm(NULL, array('title[0][value]' => $title), t('Save'));
|
||||
$node = $this->drupalGetNodeByTitle($title);
|
||||
|
||||
// Look for alias generated in the form.
|
||||
$this->drupalGet("node/{$node->id()}/edit");
|
||||
$this->assertFieldChecked('edit-path-0-pathauto');
|
||||
$this->assertFieldByName('path[0][alias]', $automatic_alias, 'Generated alias visible in the path alias field.');
|
||||
|
||||
// Check whether the alias actually works.
|
||||
$this->drupalGet($automatic_alias);
|
||||
$this->assertText($title, 'Node accessible through automatic alias.');
|
||||
|
||||
// Manually set the node's alias.
|
||||
$manual_alias = '/content/' . $node->id();
|
||||
$edit = array(
|
||||
'path[0][pathauto]' => FALSE,
|
||||
'path[0][alias]' => $manual_alias,
|
||||
);
|
||||
$this->drupalPostForm($node->toUrl('edit-form'), $edit, t('Save'));
|
||||
$this->assertText(t('@type @title has been updated.', array('@type' => 'page', '@title' => $title)));
|
||||
|
||||
// Check that the automatic alias checkbox is now unchecked by default.
|
||||
$this->drupalGet("node/{$node->id()}/edit");
|
||||
$this->assertNoFieldChecked('edit-path-0-pathauto');
|
||||
$this->assertFieldByName('path[0][alias]', $manual_alias);
|
||||
|
||||
// Submit the node form with the default values.
|
||||
$this->drupalPostForm(NULL, array('path[0][pathauto]' => FALSE), t('Save'));
|
||||
$this->assertText(t('@type @title has been updated.', array('@type' => 'page', '@title' => $title)));
|
||||
|
||||
// Test that the old (automatic) alias has been deleted and only accessible
|
||||
// through the new (manual) alias.
|
||||
$this->drupalGet($automatic_alias);
|
||||
$this->assertResponse(404, 'Node not accessible through automatic alias.');
|
||||
$this->drupalGet($manual_alias);
|
||||
$this->assertText($title, 'Node accessible through manual alias.');
|
||||
|
||||
// Test that the manual alias is not kept for new nodes when the pathauto
|
||||
// checkbox is ticked.
|
||||
$title = 'Automatic Title';
|
||||
$edit = array(
|
||||
'title[0][value]' => $title,
|
||||
'path[0][pathauto]' => TRUE,
|
||||
'path[0][alias]' => '/should-not-get-created',
|
||||
);
|
||||
$this->drupalPostForm('node/add/page', $edit, t('Save'));
|
||||
$this->assertNoAliasExists(array('alias' => 'should-not-get-created'));
|
||||
$node = $this->drupalGetNodeByTitle($title);
|
||||
$this->assertEntityAlias($node, '/content/automatic-title');
|
||||
|
||||
// Remove the pattern for nodes, the pathauto checkbox should not be
|
||||
// displayed.
|
||||
$ids = \Drupal::entityQuery('pathauto_pattern')
|
||||
->condition('type', 'canonical_entities:node')
|
||||
->execute();
|
||||
foreach (PathautoPattern::loadMultiple($ids) as $pattern) {
|
||||
$pattern->delete();
|
||||
}
|
||||
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertNoFieldById('edit-path-0-pathauto');
|
||||
$this->assertFieldByName('path[0][alias]', '');
|
||||
|
||||
$edit = array();
|
||||
$edit['title'] = 'My test article';
|
||||
$this->drupalCreateNode($edit);
|
||||
//$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$node = $this->drupalGetNodeByTitle($edit['title']);
|
||||
|
||||
// Pathauto checkbox should still not exist.
|
||||
$this->drupalGet($node->toUrl('edit-form'));
|
||||
$this->assertNoFieldById('edit-path-0-pathauto');
|
||||
$this->assertFieldByName('path[0][alias]', '');
|
||||
$this->assertNoEntityAlias($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test node operations.
|
||||
*/
|
||||
function testNodeOperations() {
|
||||
$node1 = $this->drupalCreateNode(array('title' => 'node1'));
|
||||
$node2 = $this->drupalCreateNode(array('title' => 'node2'));
|
||||
|
||||
// Delete all current URL aliases.
|
||||
$this->deleteAllAliases();
|
||||
|
||||
$this->drupalGet('admin/content');
|
||||
|
||||
// Check which of the two nodes is first.
|
||||
if (strpos($this->getTextContent(), 'node1') < strpos($this->getTextContent(), 'node2')) {
|
||||
$index = 0;
|
||||
}
|
||||
else {
|
||||
$index = 1;
|
||||
}
|
||||
|
||||
$edit = array(
|
||||
'action' => 'pathauto_update_alias_node',
|
||||
'node_bulk_form[' . $index . ']' => TRUE,
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
|
||||
$this->assertText('Update URL alias was applied to 1 item.');
|
||||
|
||||
$this->assertEntityAlias($node1, '/content/' . $node1->getTitle());
|
||||
$this->assertEntityAlias($node2, '/node/' . $node2->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Merge this with existing node test methods?
|
||||
*/
|
||||
public function testNodeState() {
|
||||
$nodeNoAliasUser = $this->drupalCreateUser(array('bypass node access'));
|
||||
$nodeAliasUser = $this->drupalCreateUser(array('bypass node access', 'create url aliases'));
|
||||
|
||||
$node = $this->drupalCreateNode(array(
|
||||
'title' => 'Node version one',
|
||||
'type' => 'page',
|
||||
'path' => array(
|
||||
'pathauto' => PathautoState::SKIP,
|
||||
),
|
||||
));
|
||||
|
||||
$this->assertNoEntityAlias($node);
|
||||
|
||||
// Set a manual path alias for the node.
|
||||
$node->path->alias = '/test-alias';
|
||||
$node->save();
|
||||
|
||||
// Ensure that the pathauto field was saved to the database.
|
||||
\Drupal::entityTypeManager()->getStorage('node')->resetCache();
|
||||
$node = Node::load($node->id());
|
||||
$this->assertIdentical($node->path->pathauto, PathautoState::SKIP);
|
||||
|
||||
// Ensure that the manual path alias was saved and an automatic alias was not generated.
|
||||
$this->assertEntityAlias($node, '/test-alias');
|
||||
$this->assertNoEntityAliasExists($node, '/content/node-version-one');
|
||||
|
||||
// Save the node as a user who does not have access to path fieldset.
|
||||
$this->drupalLogin($nodeNoAliasUser);
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
$this->assertNoFieldByName('path[0][pathauto]');
|
||||
|
||||
$edit = array('title[0][value]' => 'Node version two');
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertText('Basic page Node version two has been updated.');
|
||||
|
||||
$this->assertEntityAlias($node, '/test-alias');
|
||||
$this->assertNoEntityAliasExists($node, '/content/node-version-one');
|
||||
$this->assertNoEntityAliasExists($node, '/content/node-version-two');
|
||||
|
||||
// Load the edit node page and check that the Pathauto checkbox is unchecked.
|
||||
$this->drupalLogin($nodeAliasUser);
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
$this->assertNoFieldChecked('edit-path-0-pathauto');
|
||||
|
||||
// Edit the manual alias and save the node.
|
||||
$edit = array(
|
||||
'title[0][value]' => 'Node version three',
|
||||
'path[0][alias]' => '/manually-edited-alias',
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertText('Basic page Node version three has been updated.');
|
||||
|
||||
$this->assertEntityAlias($node, '/manually-edited-alias');
|
||||
$this->assertNoEntityAliasExists($node, '/test-alias');
|
||||
$this->assertNoEntityAliasExists($node, '/content/node-version-one');
|
||||
$this->assertNoEntityAliasExists($node, '/content/node-version-two');
|
||||
$this->assertNoEntityAliasExists($node, '/content/node-version-three');
|
||||
|
||||
// Programatically save the node with an automatic alias.
|
||||
\Drupal::entityTypeManager()->getStorage('node')->resetCache();
|
||||
$node = Node::load($node->id());
|
||||
$node->path->pathauto = PathautoState::CREATE;
|
||||
$node->save();
|
||||
|
||||
// Ensure that the pathauto field was saved to the database.
|
||||
\Drupal::entityTypeManager()->getStorage('node')->resetCache();
|
||||
$node = Node::load($node->id());
|
||||
$this->assertIdentical($node->path->pathauto, PathautoState::CREATE);
|
||||
|
||||
$this->assertEntityAlias($node, '/content/node-version-three');
|
||||
$this->assertNoEntityAliasExists($node, '/manually-edited-alias');
|
||||
$this->assertNoEntityAliasExists($node, '/test-alias');
|
||||
$this->assertNoEntityAliasExists($node, '/content/node-version-one');
|
||||
$this->assertNoEntityAliasExists($node, '/content/node-version-two');
|
||||
|
||||
$node->delete();
|
||||
$this->assertNull(\Drupal::keyValue('pathauto_state.node')->get($node->id()), 'Pathauto state was deleted');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that nodes without a Pathauto pattern can set custom aliases.
|
||||
*/
|
||||
public function testCustomAliasWithoutPattern() {
|
||||
// First, delete all patterns to be sure that there will be no match.
|
||||
$entity_ids = \Drupal::entityQuery('pathauto_pattern')->execute();
|
||||
$entities = PathautoPattern::loadMultiple($entity_ids);
|
||||
foreach ($entities as $entity) {
|
||||
$entity->delete();
|
||||
}
|
||||
|
||||
// Next, create a node with a custom alias.
|
||||
$edit = [
|
||||
'title[0][value]' => 'Sample article',
|
||||
'path[0][alias]' => '/sample-article',
|
||||
];
|
||||
$this->drupalPostForm('node/add/article', $edit, t('Save'));
|
||||
$this->assertText(t('article Sample article has been created.'));
|
||||
|
||||
// Test the alias.
|
||||
$this->assertAliasExists(array('alias' => '/sample-article'));
|
||||
$this->drupalGet('sample-article');
|
||||
$this->assertResponse(200, 'A node without a pattern can have a custom alias.');
|
||||
|
||||
// Now create a node through the API.
|
||||
$node = Node::create(['type' => 'article', 'title' => 'Sample article API', 'path' => ['alias' => '/sample-article-api']]);
|
||||
$node->save();
|
||||
|
||||
// Test the alias.
|
||||
$this->assertAliasExists(['alias' => '/sample-article-api']);
|
||||
$this->drupalGet('sample-article-api');
|
||||
$this->assertResponse(200);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Tests;
|
||||
|
||||
use Drupal\pathauto\PathautoGeneratorInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests pathauto settings form.
|
||||
*
|
||||
* @group pathauto
|
||||
*/
|
||||
class PathautoSettingsFormWebTest extends WebTestBase {
|
||||
|
||||
use PathautoTestHelperTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node', 'pathauto');
|
||||
|
||||
/**
|
||||
* Admin user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* Form values that are set by default.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFormValues = array(
|
||||
'verbose' => FALSE,
|
||||
'separator' => '-',
|
||||
'case' => '1',
|
||||
'max_length' => '100',
|
||||
'max_component_length' => '100',
|
||||
'update_action' => '2',
|
||||
'transliterate' => '1',
|
||||
'reduce_ascii' => FALSE,
|
||||
'ignore_words' => 'a, an, as, at, before, but, by, for, from, is, in, into, like, of, off, on, onto, per, since, than, the, this, that, to, up, via, with',
|
||||
);
|
||||
|
||||
/**
|
||||
* Punctuation form items with default values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultPunctuations = array(
|
||||
'punctuation[double_quotes]' => '0',
|
||||
'punctuation[quotes]' => '0',
|
||||
'punctuation[backtick]' => '0',
|
||||
'punctuation[comma]' => '0',
|
||||
'punctuation[period]' => '0',
|
||||
'punctuation[hyphen]' => '1',
|
||||
'punctuation[underscore]' => '0',
|
||||
'punctuation[colon]' => '0',
|
||||
'punctuation[semicolon]' => '0',
|
||||
'punctuation[pipe]' => '0',
|
||||
'punctuation[left_curly]' => '0',
|
||||
'punctuation[left_square]' => '0',
|
||||
'punctuation[right_curly]' => '0',
|
||||
'punctuation[right_square]' => '0',
|
||||
'punctuation[plus]' => '0',
|
||||
'punctuation[equal]' => '0',
|
||||
'punctuation[asterisk]' => '0',
|
||||
'punctuation[ampersand]' => '0',
|
||||
'punctuation[percent]' => '0',
|
||||
'punctuation[caret]' => '0',
|
||||
'punctuation[dollar]' => '0',
|
||||
'punctuation[hash]' => '0',
|
||||
'punctuation[exclamation]' => '0',
|
||||
'punctuation[tilde]' => '0',
|
||||
'punctuation[left_parenthesis]' => '0',
|
||||
'punctuation[right_parenthesis]' => '0',
|
||||
'punctuation[question_mark]' => '0',
|
||||
'punctuation[less_than]' => '0',
|
||||
'punctuation[greater_than]' => '0',
|
||||
'punctuation[slash]' => '0',
|
||||
'punctuation[back_slash]' => '0',
|
||||
);
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(array('type' => 'article'));
|
||||
|
||||
$permissions = array(
|
||||
'administer pathauto',
|
||||
'notify of path changes',
|
||||
'administer url aliases',
|
||||
'create url aliases',
|
||||
'bypass node access',
|
||||
);
|
||||
$this->adminUser = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->createPattern('node', '/content/[node:title]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the default values are shown correctly in the form.
|
||||
*/
|
||||
function testDefaultFormValues() {
|
||||
$this->drupalGet('/admin/config/search/path/settings');
|
||||
$this->assertNoFieldChecked('edit-verbose');
|
||||
$this->assertField('edit-separator', $this->defaultFormValues['separator']);
|
||||
$this->assertFieldChecked('edit-case');
|
||||
$this->assertField('edit-max-length', $this->defaultFormValues['max_length']);
|
||||
$this->assertField('edit-max-component-length', $this->defaultFormValues['max_component_length']);
|
||||
$this->assertFieldChecked('edit-update-action-2');
|
||||
$this->assertFieldChecked('edit-transliterate');
|
||||
$this->assertNoFieldChecked('edit-reduce-ascii');
|
||||
$this->assertField('edit-ignore-words', $this->defaultFormValues['ignore_words']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the verbose option.
|
||||
*/
|
||||
function testVerboseOption() {
|
||||
$edit = array('verbose' => '1');
|
||||
$this->drupalPostForm('/admin/config/search/path/settings', $edit, t('Save configuration'));
|
||||
$this->assertText(t('The configuration options have been saved.'));
|
||||
$this->assertFieldChecked('edit-verbose');
|
||||
|
||||
$title = 'Verbose settings test';
|
||||
$this->drupalGet('/node/add/article');
|
||||
$this->assertFieldChecked('edit-path-0-pathauto');
|
||||
$this->drupalPostForm(NULL, array('title[0][value]' => $title), t('Save'));
|
||||
$this->assertText('Created new alias /content/verbose-settings-test for');
|
||||
|
||||
$node = $this->drupalGetNodeByTitle($title);
|
||||
$this->drupalPostForm('/node/' . $node->id() . '/edit', array('title[0][value]' => 'Updated title'), t('Save'));
|
||||
$this->assertText('Created new alias /content/updated-title for');
|
||||
$this->assertText('replacing /content/verbose-settings-test.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests generating aliases with different settings.
|
||||
*/
|
||||
function testSettingsForm() {
|
||||
// Ensure the separator settings apply correctly.
|
||||
$this->checkAlias('My awesome content', '/content/my.awesome.content', array('separator' => '.'));
|
||||
|
||||
// Ensure the character case setting works correctly.
|
||||
// Leave case the same as source token values.
|
||||
$this->checkAlias('My awesome Content', '/content/My-awesome-Content', array('case' => FALSE));
|
||||
$this->checkAlias('Change Lower', '/content/change-lower', array('case' => '1'));
|
||||
|
||||
// Ensure the maximum alias length is working.
|
||||
$this->checkAlias('My awesome Content', '/content/my-awesome', array('max_length' => '23'));
|
||||
|
||||
// Ensure the maximum component length is working.
|
||||
$this->checkAlias('My awesome Content', '/content/my', array('max_component_length' => '2'));
|
||||
|
||||
// Ensure transliteration option is working.
|
||||
$this->checkAlias('è é àl ö äl ü', '/content/e-e-al-o-al-u', array('transliterate' => '1'));
|
||||
$this->checkAlias('è é àl äl ö ü', '/content/è-é-àl-äl-ö-ü', array('transliterate' => FALSE));
|
||||
|
||||
$ignore_words = 'a, new, very, should';
|
||||
$this->checkAlias('a very new alias to test', '/content/alias-to-test', array('ignore_words' => $ignore_words));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the punctuation setting form items.
|
||||
*/
|
||||
function testPunctuationSettings() {
|
||||
// Test the replacement of punctuations.
|
||||
$settings = [];
|
||||
foreach ($this->defaultPunctuations as $key => $punctuation) {
|
||||
$settings[$key] = PathautoGeneratorInterface::PUNCTUATION_REPLACE;
|
||||
}
|
||||
|
||||
$title = 'aa"b`c,d.e-f_g:h;i|j{k[l}m]n+o=p*q%r^s$t#u!v~w(x)y?z>1/2\3';
|
||||
$alias = '/content/aa-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-1-2-3';
|
||||
$this->checkAlias($title, $alias, $settings);
|
||||
|
||||
// Test the removal of punctuations.
|
||||
$settings = [];
|
||||
foreach ($this->defaultPunctuations as $key => $punctuation) {
|
||||
$settings[$key] = PathautoGeneratorInterface::PUNCTUATION_REMOVE;
|
||||
}
|
||||
|
||||
$title = 'a"b`c,d.e-f_g:h;i|j{k[l}m]n+o=p*q%r^s$t#u!v~w(x)y?z>1/2\3';
|
||||
$alias = '/content/abcdefghijklmnopqrstuvwxyz123';
|
||||
$this->checkAlias($title, $alias, $settings);
|
||||
|
||||
// Keep all punctuations in alias.
|
||||
$settings = [];
|
||||
foreach ($this->defaultPunctuations as $key => $punctuation) {
|
||||
$settings[$key] = PathautoGeneratorInterface::PUNCTUATION_DO_NOTHING;
|
||||
}
|
||||
|
||||
$title = 'al"b`c,d.e-f_g:h;i|j{k[l}m]n+o=p*q%r^s$t#u!v~w(x)y?z>1/2\3';
|
||||
$alias = '/content/al"b`c,d.e-f_g:h;i|j{k[l}m]n+o=p*q%r^s$t#u!v~w(x)y?z>1/2\3';
|
||||
$this->checkAlias($title, $alias, $settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to check the an aliases.
|
||||
*
|
||||
* @param string $title
|
||||
* The node title to build the aliases from.
|
||||
* @param string $alias
|
||||
* The expected alias.
|
||||
* @param array $settings
|
||||
* The form values the alias should be generated with.
|
||||
*/
|
||||
protected function checkAlias($title, $alias, $settings = array()) {
|
||||
// Submit the settings form.
|
||||
$edit = array_merge($this->defaultFormValues + $this->defaultPunctuations, $settings);
|
||||
$this->drupalPostForm('/admin/config/search/path/settings', $edit, t('Save configuration'));
|
||||
$this->assertText(t('The configuration options have been saved.'));
|
||||
|
||||
// If we do not clear the caches here, AliasCleaner will use its
|
||||
// cleanStringCache instance variable. Due to that the creation of aliases
|
||||
// with $this->createNode() will only work correctly on the first call.
|
||||
\Drupal::service('pathauto.generator')->resetCaches();
|
||||
|
||||
// Create a node and check if the settings applied.
|
||||
$node = $this->createNode(
|
||||
array(
|
||||
'title' => $title,
|
||||
'type' => 'article',
|
||||
)
|
||||
);
|
||||
|
||||
$this->drupalGet($alias);
|
||||
$this->assertResponse(200);
|
||||
$this->assertEntityAlias($node, $alias);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Tests;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests pathauto taxonomy UI integration.
|
||||
*
|
||||
* @group pathauto
|
||||
*/
|
||||
class PathautoTaxonomyWebTest extends WebTestBase {
|
||||
|
||||
use PathautoTestHelperTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('taxonomy', 'pathauto', 'views');
|
||||
|
||||
/**
|
||||
* Admin user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Allow other modules to add additional permissions for the admin user.
|
||||
$permissions = array(
|
||||
'administer pathauto',
|
||||
'administer url aliases',
|
||||
'create url aliases',
|
||||
'administer taxonomy',
|
||||
);
|
||||
$this->adminUser = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
$this->createPattern('taxonomy_term', '/[term:vocabulary]/[term:name]');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Basic functional testing of Pathauto with taxonomy terms.
|
||||
*/
|
||||
function testTermEditing() {
|
||||
$this->drupalGet('admin/structure');
|
||||
$this->drupalGet('admin/structure/taxonomy');
|
||||
|
||||
// Add vocabulary "tags".
|
||||
$vocabulary = $this->addVocabulary(array('name' => 'tags', 'vid' => 'tags'));
|
||||
|
||||
// Create term for testing.
|
||||
$name = 'Testing: term name [';
|
||||
$automatic_alias = '/tags/testing-term-name';
|
||||
$this->drupalPostForm('admin/structure/taxonomy/manage/tags/add', array('name[0][value]' => $name), 'Save');
|
||||
$name = trim($name);
|
||||
$this->assertText("Created new term $name.");
|
||||
$term = $this->drupalGetTermByName($name);
|
||||
|
||||
// Look for alias generated in the form.
|
||||
$this->drupalGet("taxonomy/term/{$term->id()}/edit");
|
||||
$this->assertFieldChecked('edit-path-0-pathauto');
|
||||
$this->assertFieldByName('path[0][alias]', $automatic_alias, 'Generated alias visible in the path alias field.');
|
||||
|
||||
// Check whether the alias actually works.
|
||||
$this->drupalGet($automatic_alias);
|
||||
$this->assertText($name, 'Term accessible through automatic alias.');
|
||||
|
||||
// Manually set the term's alias.
|
||||
$manual_alias = '/tags/' . $term->id();
|
||||
$edit = array(
|
||||
'path[0][pathauto]' => FALSE,
|
||||
'path[0][alias]' => $manual_alias,
|
||||
);
|
||||
$this->drupalPostForm("taxonomy/term/{$term->id()}/edit", $edit, t('Save'));
|
||||
$this->assertText("Updated term $name.");
|
||||
|
||||
// Check that the automatic alias checkbox is now unchecked by default.
|
||||
$this->drupalGet("taxonomy/term/{$term->id()}/edit");
|
||||
$this->assertNoFieldChecked('edit-path-0-pathauto');
|
||||
$this->assertFieldByName('path[0][alias]', $manual_alias);
|
||||
|
||||
// Submit the term form with the default values.
|
||||
$this->drupalPostForm(NULL, array('path[0][pathauto]' => FALSE), t('Save'));
|
||||
$this->assertText("Updated term $name.");
|
||||
|
||||
// Test that the old (automatic) alias has been deleted and only accessible
|
||||
// through the new (manual) alias.
|
||||
$this->drupalGet($automatic_alias);
|
||||
$this->assertResponse(404, 'Term not accessible through automatic alias.');
|
||||
$this->drupalGet($manual_alias);
|
||||
$this->assertText($name, 'Term accessible through manual alias.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Tests;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\pathauto\Entity\PathautoPattern;
|
||||
use Drupal\pathauto\PathautoPatternInterface;
|
||||
use Drupal\taxonomy\VocabularyInterface;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
|
||||
/**
|
||||
* Helper test class with some added functions for testing.
|
||||
*/
|
||||
trait PathautoTestHelperTrait {
|
||||
|
||||
/**
|
||||
* Creates a pathauto pattern.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type.
|
||||
* @param string $pattern
|
||||
* The path pattern.
|
||||
* @param int $weight
|
||||
* (optional) The pattern weight.
|
||||
*
|
||||
* @return \Drupal\pathauto\PathautoPatternInterface
|
||||
* The created pattern.
|
||||
*/
|
||||
protected function createPattern($entity_type_id, $pattern, $weight = 10) {
|
||||
$type = ($entity_type_id == 'forum') ? 'forum' : 'canonical_entities:' . $entity_type_id;
|
||||
|
||||
$pattern = PathautoPattern::create([
|
||||
'id' => Unicode::strtolower($this->randomMachineName()),
|
||||
'type' => $type,
|
||||
'pattern' => $pattern,
|
||||
'weight' => $weight,
|
||||
]);
|
||||
$pattern->save();
|
||||
return $pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a bundle condition to a pathauto pattern.
|
||||
*
|
||||
* @param \Drupal\pathauto\PathautoPatternInterface $pattern
|
||||
* The pattern.
|
||||
* @param string $entity_type
|
||||
* The entity type ID.
|
||||
* @param string $bundle
|
||||
* The bundle
|
||||
*/
|
||||
protected function addBundleCondition(PathautoPatternInterface $pattern, $entity_type, $bundle) {
|
||||
$plugin_id = $entity_type == 'node' ? 'node_type' : 'entity_bundle:' . $entity_type;
|
||||
|
||||
$pattern->addSelectionCondition(
|
||||
[
|
||||
'id' => $plugin_id,
|
||||
'bundles' => [
|
||||
$bundle => $bundle,
|
||||
],
|
||||
'negate' => FALSE,
|
||||
'context_mapping' => [
|
||||
$entity_type => $entity_type,
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function assertToken($type, $object, $token, $expected) {
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$tokens = \Drupal::token()->generate($type, array($token => $token), array($type => $object), [], $bubbleable_metadata);
|
||||
$tokens += array($token => '');
|
||||
$this->assertIdentical($tokens[$token], $expected, t("Token value for [@type:@token] was '@actual', expected value '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $tokens[$token], '@expected' => $expected)));
|
||||
}
|
||||
|
||||
public function saveAlias($source, $alias, $langcode = Language::LANGCODE_NOT_SPECIFIED) {
|
||||
\Drupal::service('path.alias_storage')->delete(array('source' => $source, 'language', 'langcode' => $langcode));
|
||||
return \Drupal::service('path.alias_storage')->save($source, $alias, $langcode);
|
||||
}
|
||||
|
||||
public function saveEntityAlias(EntityInterface $entity, $alias, $langcode = NULL) {
|
||||
// By default, use the entity language.
|
||||
if (!$langcode) {
|
||||
$langcode = $entity->language()->getId();
|
||||
}
|
||||
return $this->saveAlias('/' . $entity->toUrl()->getInternalPath(), $alias, $langcode);
|
||||
}
|
||||
|
||||
public function assertEntityAlias(EntityInterface $entity, $expected_alias, $langcode = NULL) {
|
||||
// By default, use the entity language.
|
||||
if (!$langcode) {
|
||||
$langcode = $entity->language()->getId();
|
||||
}
|
||||
$this->assertAlias('/' . $entity->toUrl()->getInternalPath(), $expected_alias, $langcode);
|
||||
}
|
||||
|
||||
public function assertEntityAliasExists(EntityInterface $entity) {
|
||||
return $this->assertAliasExists(array('source' => '/' . $entity->toUrl()->getInternalPath()));
|
||||
}
|
||||
|
||||
public function assertNoEntityAlias(EntityInterface $entity, $langcode = NULL) {
|
||||
// By default, use the entity language.
|
||||
if (!$langcode) {
|
||||
$langcode = $entity->language()->getId();
|
||||
}
|
||||
$this->assertEntityAlias($entity, '/' . $entity->toUrl()->getInternalPath(), $langcode);
|
||||
}
|
||||
|
||||
public function assertNoEntityAliasExists(EntityInterface $entity, $alias = NULL) {
|
||||
$path = array('source' => '/' . $entity->toUrl()->getInternalPath());
|
||||
if (!empty($alias)) {
|
||||
$path['alias'] = $alias;
|
||||
}
|
||||
$this->assertNoAliasExists($path);
|
||||
}
|
||||
|
||||
public function assertAlias($source, $expected_alias, $langcode = Language::LANGCODE_NOT_SPECIFIED) {
|
||||
\Drupal::service('path.alias_manager')->cacheClear($source);
|
||||
$this->assertEqual($expected_alias, \Drupal::service('path.alias_manager')->getAliasByPath($source, $langcode), t("Alias for %source with language '@language' is correct.",
|
||||
array('%source' => $source, '@language' => $langcode)));
|
||||
}
|
||||
|
||||
public function assertAliasExists($conditions) {
|
||||
$path = \Drupal::service('path.alias_storage')->load($conditions);
|
||||
$this->assertTrue($path, t('Alias with conditions @conditions found.', array('@conditions' => var_export($conditions, TRUE))));
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function assertNoAliasExists($conditions) {
|
||||
$alias = \Drupal::service('path.alias_storage')->load($conditions);
|
||||
$this->assertFalse($alias, t('Alias with conditions @conditions not found.', array('@conditions' => var_export($conditions, TRUE))));
|
||||
}
|
||||
|
||||
public function deleteAllAliases() {
|
||||
\Drupal::database()->delete('url_alias')->execute();
|
||||
\Drupal::service('path.alias_manager')->cacheClear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $values
|
||||
* @return \Drupal\taxonomy\VocabularyInterface
|
||||
*/
|
||||
public function addVocabulary(array $values = array()) {
|
||||
$name = Unicode::strtolower($this->randomMachineName(5));
|
||||
$values += array(
|
||||
'name' => $name,
|
||||
'vid' => $name,
|
||||
);
|
||||
$vocabulary = Vocabulary::create($values);
|
||||
$vocabulary->save();
|
||||
|
||||
return $vocabulary;
|
||||
}
|
||||
|
||||
public function addTerm(VocabularyInterface $vocabulary, array $values = array()) {
|
||||
$values += array(
|
||||
'name' => Unicode::strtolower($this->randomMachineName(5)),
|
||||
'vid' => $vocabulary->id(),
|
||||
);
|
||||
|
||||
$term = Term::create($values);
|
||||
$term->save();
|
||||
return $term;
|
||||
}
|
||||
|
||||
public function assertEntityPattern($entity_type, $bundle, $langcode = Language::LANGCODE_NOT_SPECIFIED, $expected) {
|
||||
|
||||
$values = [
|
||||
'langcode' => $langcode,
|
||||
\Drupal::entityTypeManager()->getDefinition($entity_type)->getKey('bundle') => $bundle,
|
||||
];
|
||||
$entity = \Drupal::entityTypeManager()->getStorage($entity_type)->create($values);
|
||||
|
||||
$pattern = \Drupal::service('pathauto.generator')->getPatternByEntity($entity);
|
||||
$this->assertIdentical($expected, $pattern->getPattern());
|
||||
}
|
||||
|
||||
public function drupalGetTermByName($name, $reset = FALSE) {
|
||||
if ($reset) {
|
||||
// @todo - implement cache reset.
|
||||
}
|
||||
$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties(array('name' => $name));
|
||||
return !empty($terms) ? reset($terms) : FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Tests;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\pathauto\Entity\PathautoPattern;
|
||||
|
||||
/**
|
||||
* Test basic pathauto functionality.
|
||||
*
|
||||
* @group pathauto
|
||||
*/
|
||||
class PathautoUiTest extends WebTestBase {
|
||||
|
||||
use PathautoTestHelperTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('pathauto', 'node');
|
||||
|
||||
/**
|
||||
* Admin user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
|
||||
$this->drupalCreateContentType(array('type' => 'article'));
|
||||
|
||||
// Allow other modules to add additional permissions for the admin user.
|
||||
$permissions = array(
|
||||
'administer pathauto',
|
||||
'administer url aliases',
|
||||
'create url aliases',
|
||||
'administer nodes',
|
||||
'bypass node access',
|
||||
'access content overview',
|
||||
);
|
||||
$this->adminUser = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
function testSettingsValidation() {
|
||||
$edit = array();
|
||||
$edit['max_length'] = 'abc';
|
||||
$edit['max_component_length'] = 'abc';
|
||||
$this->drupalPostForm('admin/config/search/path/settings', $edit, 'Save configuration');
|
||||
/*$this->assertText('The field Maximum alias length is not a valid number.');
|
||||
$this->assertText('The field Maximum component length is not a valid number.');*/
|
||||
$this->assertNoText('The configuration options have been saved.');
|
||||
|
||||
$edit['max_length'] = '0';
|
||||
$edit['max_component_length'] = '0';
|
||||
$this->drupalPostForm('admin/config/search/path/settings', $edit, 'Save configuration');
|
||||
/*$this->assertText('The field Maximum alias length cannot be less than 1.');
|
||||
$this->assertText('The field Maximum component length cannot be less than 1.');*/
|
||||
$this->assertNoText('The configuration options have been saved.');
|
||||
|
||||
$edit['max_length'] = '999';
|
||||
$edit['max_component_length'] = '999';
|
||||
$this->drupalPostForm('admin/config/search/path/settings', $edit, 'Save configuration');
|
||||
/*$this->assertText('The field Maximum alias length cannot be greater than 255.');
|
||||
$this->assertText('The field Maximum component length cannot be greater than 255.');*/
|
||||
$this->assertNoText('The configuration options have been saved.');
|
||||
|
||||
$edit['max_length'] = '50';
|
||||
$edit['max_component_length'] = '50';
|
||||
$this->drupalPostForm('admin/config/search/path/settings', $edit, 'Save configuration');
|
||||
$this->assertText('The configuration options have been saved.');
|
||||
}
|
||||
|
||||
function testPatternsWorkflow() {
|
||||
// Try to save an empty pattern, should not be allowed.
|
||||
$this->drupalGet('admin/config/search/path/patterns/add');
|
||||
$edit = array(
|
||||
'type' => 'canonical_entities:node',
|
||||
);
|
||||
$this->drupalPostAjaxForm(NULL, $edit, 'type');
|
||||
$edit += array(
|
||||
'bundles[page]' => TRUE,
|
||||
'label' => 'Page pattern',
|
||||
'id' => 'page_pattern',
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertText('Path pattern field is required.');
|
||||
$this->assertNoText('The configuration options have been saved.');
|
||||
|
||||
// Try to save an invalid pattern.
|
||||
$edit += array(
|
||||
'pattern' => '[node:title]/[user:name]/[term:name]',
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertText('Path pattern is using the following invalid tokens: [user:name], [term:name].');
|
||||
$this->assertNoText('The configuration options have been saved.');
|
||||
|
||||
$edit['pattern'] = '#[node:title]';
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertText('The Path pattern is using the following invalid characters: #.');
|
||||
$this->assertNoText('The configuration options have been saved.');
|
||||
|
||||
// Checking whitespace ending of the string.
|
||||
$edit['pattern'] = '[node:title] ';
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertText('The Path pattern doesn\'t allow the patterns ending with whitespace.');
|
||||
$this->assertNoText('The configuration options have been saved.');
|
||||
|
||||
// Fix the pattern, then check that it gets saved successfully.
|
||||
$edit['pattern'] = '[node:title]';
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertText('Pattern Page pattern saved.');
|
||||
|
||||
\Drupal::service('pathauto.generator')->resetCaches();
|
||||
|
||||
// Create a node with pattern enabled and check if the pattern applies.
|
||||
$title = 'Page Pattern enabled';
|
||||
$alias = '/page-pattern-enabled';
|
||||
$node = $this->createNode(['title' => $title, 'type' => 'page']);
|
||||
$this->drupalGet($alias);
|
||||
$this->assertResponse(200);
|
||||
$this->assertEntityAlias($node, $alias);
|
||||
|
||||
// Edit workflow, set a new label and weight for the pattern.
|
||||
$this->drupalPostForm('/admin/config/search/path/patterns', ['entities[page_pattern][weight]' => '4'], t('Save'));
|
||||
$this->clickLink(t('Edit'));
|
||||
$destination_query = ['query' => ['destination' => Url::fromRoute('entity.pathauto_pattern.collection')->toString()]];
|
||||
$this->assertUrl('/admin/config/search/path/patterns/page_pattern', $destination_query);
|
||||
$this->assertFieldByName('pattern', '[node:title]');
|
||||
$this->assertFieldByName('label', 'Page pattern');
|
||||
$this->assertFieldChecked('edit-status');
|
||||
$this->assertLink(t('Delete'));
|
||||
|
||||
$edit = array('label' => 'Test');
|
||||
$this->drupalPostForm('/admin/config/search/path/patterns/page_pattern', $edit, t('Save'));
|
||||
$this->assertText('Pattern Test saved.');
|
||||
// Check that the pattern weight did not change.
|
||||
$this->assertOptionSelected('edit-entities-page-pattern-weight', '4');
|
||||
|
||||
// Disable workflow.
|
||||
$this->drupalGet('/admin/config/search/path/patterns');
|
||||
$this->assertNoLink(t('Enable'));
|
||||
$this->clickLink(t('Disable'));
|
||||
$this->assertUrl('/admin/config/search/path/patterns/page_pattern/disable', $destination_query);
|
||||
$this->drupalPostForm(NULL, [], t('Disable'));
|
||||
$this->assertText('Disabled pattern Test.');
|
||||
|
||||
// Load the pattern from storage and check if its disabled.
|
||||
$pattern = PathautoPattern::load('page_pattern');
|
||||
$this->assertFalse($pattern->status());
|
||||
|
||||
\Drupal::service('pathauto.generator')->resetCaches();
|
||||
|
||||
// Create a node with pattern disabled and check that we have no new alias.
|
||||
$title = 'Page Pattern disabled';
|
||||
$node = $this->createNode(['title' => $title, 'type' => 'page']);
|
||||
$this->assertNoEntityAlias($node);
|
||||
|
||||
// Enable workflow.
|
||||
$this->drupalGet('/admin/config/search/path/patterns');
|
||||
$this->assertNoLink(t('Disable'));
|
||||
$this->clickLink(t('Enable'));
|
||||
$this->assertUrl('/admin/config/search/path/patterns/page_pattern/enable', $destination_query);
|
||||
$this->drupalPostForm(NULL, [], t('Enable'));
|
||||
$this->assertText('Enabled pattern Test.');
|
||||
|
||||
// Reload pattern from storage and check if its enabled.
|
||||
$pattern = PathautoPattern::load('page_pattern');
|
||||
$this->assertTrue($pattern->status());
|
||||
|
||||
// Delete workflow.
|
||||
$this->drupalGet('/admin/config/search/path/patterns');
|
||||
$this->clickLink(t('Delete'));
|
||||
$this->assertUrl('/admin/config/search/path/patterns/page_pattern/delete', $destination_query);
|
||||
$this->assertText(t('This action cannot be undone.'));
|
||||
$this->drupalPostForm(NULL, [], t('Delete'));
|
||||
$this->assertText('The pathauto pattern Test has been deleted.');
|
||||
|
||||
$this->assertFalse(PathautoPattern::load('page_pattern'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto\Tests;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests pathauto user UI integration.
|
||||
*
|
||||
* @group pathauto
|
||||
*/
|
||||
class PathautoUserWebTest extends WebTestBase {
|
||||
|
||||
use PathautoTestHelperTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('pathauto', 'views');
|
||||
|
||||
/**
|
||||
* Admin user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Allow other modules to add additional permissions for the admin user.
|
||||
$permissions = array(
|
||||
'administer pathauto',
|
||||
'administer url aliases',
|
||||
'create url aliases',
|
||||
'administer users',
|
||||
);
|
||||
$this->adminUser = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
$this->createPattern('user', '/users/[user:name]');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Basic functional testing of Pathauto with users.
|
||||
*/
|
||||
function testUserEditing() {
|
||||
// There should be no Pathauto checkbox on user forms.
|
||||
$this->drupalGet('user/' . $this->adminUser->id() . '/edit');
|
||||
$this->assertNoFieldById('path[0][pathauto]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user operations.
|
||||
*/
|
||||
function testUserOperations() {
|
||||
$account = $this->drupalCreateUser();
|
||||
|
||||
// Delete all current URL aliases.
|
||||
$this->deleteAllAliases();
|
||||
|
||||
// Find the position of just created account in the user_admin_people view.
|
||||
$view = Views::getView('user_admin_people');
|
||||
$view->initDisplay();
|
||||
$view->preview('page_1');
|
||||
|
||||
|
||||
foreach ($view->result as $key => $row) {
|
||||
if ($view->field['name']->getValue($row) == $account->getUsername()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$edit = array(
|
||||
'action' => 'pathauto_update_alias_user',
|
||||
"user_bulk_form[$key]" => TRUE,
|
||||
);
|
||||
$this->drupalPostForm('admin/people', $edit, t('Apply to selected items'));
|
||||
$this->assertText('Update URL alias was applied to 1 item.');
|
||||
|
||||
$this->assertEntityAlias($account, '/users/' . Unicode::strtolower($account->getUsername()));
|
||||
$this->assertEntityAlias($this->adminUser, '/user/' . $this->adminUser->id());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Provides a verbose messenger.
|
||||
*/
|
||||
class VerboseMessenger implements MessengerInterface {
|
||||
|
||||
/**
|
||||
* The verbose flag.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isVerbose;
|
||||
|
||||
/**
|
||||
* The config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The current user account.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* Creates a verbose messenger.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, AccountInterface $account) {
|
||||
$this->configFactory = $config_factory;
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addMessage($message, $op = NULL) {
|
||||
|
||||
if (!isset($this->isVerbose)) {
|
||||
$config = $this->configFactory->get('pathauto.settings');
|
||||
$this->isVerbose = $config->get('verbose') && $this->account->hasPermission('notify of path changes');
|
||||
}
|
||||
|
||||
if (!$this->isVerbose || (isset($op) && in_array($op, array('bulkupdate', 'return')))) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ($message) {
|
||||
drupal_set_message($message);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
name: 'Pathauto testing module'
|
||||
type: module
|
||||
# core: '8.x'
|
||||
description: 'Pathauto for Entity with string ID.'
|
||||
package: Testing
|
||||
dependencies:
|
||||
- token
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-03-03
|
||||
version: '8.x-1.1'
|
||||
core: '8.x'
|
||||
project: 'pathauto'
|
||||
datestamp: 1520092688
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\pathauto_string_id_test\Entity;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
|
||||
/**
|
||||
* Defines a test entity with a string ID.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "pathauto_string_id_test",
|
||||
* label = @Translation("Test entity with string ID"),
|
||||
* handlers = {
|
||||
* "route_provider" = {
|
||||
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
|
||||
* },
|
||||
* },
|
||||
* base_table = "pathauto_string_id_test",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "name",
|
||||
* },
|
||||
* links = {
|
||||
* "canonical" = "/pathauto_string_id_test/{pathauto_string_id_test}",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class PathautoStringIdTest extends ContentEntityBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
$fields['id'] = BaseFieldDefinition::create('string')
|
||||
->setLabel('ID')
|
||||
->setReadOnly(TRUE)
|
||||
// A bigger value will not be allowed to build the index.
|
||||
->setSetting('max_length', 191);
|
||||
$fields['name'] = BaseFieldDefinition::create('string')
|
||||
->setLabel('Name');
|
||||
$fields['path'] = BaseFieldDefinition::create('path')
|
||||
->setLabel('Path')
|
||||
->setComputed(TRUE);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- taxonomy
|
||||
- user
|
||||
id: articles
|
||||
label: articles
|
||||
module: pathauto_views_test
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: '‹ Previous'
|
||||
next: 'Next ›'
|
||||
first: '« First'
|
||||
last: 'Last »'
|
||||
quantity: 9
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: 'entity:node'
|
||||
options:
|
||||
view_mode: teaser
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
settings:
|
||||
link_to_entity: true
|
||||
plugin_id: field
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
filters:
|
||||
status:
|
||||
value: '1'
|
||||
table: node_field_data
|
||||
field: status
|
||||
plugin_id: boolean
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
value:
|
||||
article: article
|
||||
entity_type: node
|
||||
entity_field: type
|
||||
plugin_id: bundle
|
||||
sorts:
|
||||
created:
|
||||
id: created
|
||||
table: node_field_data
|
||||
field: created
|
||||
order: DESC
|
||||
entity_type: node
|
||||
entity_field: created
|
||||
plugin_id: date
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
granularity: second
|
||||
title: articles
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments:
|
||||
tid:
|
||||
id: tid
|
||||
table: taxonomy_index
|
||||
field: tid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
default_action: ignore
|
||||
exception:
|
||||
value: all
|
||||
title_enable: false
|
||||
title: All
|
||||
title_enable: false
|
||||
title: ''
|
||||
default_argument_type: fixed
|
||||
default_argument_options:
|
||||
argument: ''
|
||||
default_argument_skip_url: false
|
||||
summary_options:
|
||||
base_path: ''
|
||||
count: true
|
||||
items_per_page: 25
|
||||
override: false
|
||||
summary:
|
||||
sort_order: asc
|
||||
number_of_records: 0
|
||||
format: default_summary
|
||||
specify_validation: false
|
||||
validate:
|
||||
type: none
|
||||
fail: 'not found'
|
||||
validate_options: { }
|
||||
break_phrase: false
|
||||
add_table: false
|
||||
require_value: false
|
||||
reduce_duplicates: false
|
||||
plugin_id: taxonomy_index_tid
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: articles
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
name: 'Views Test Config'
|
||||
type: module
|
||||
description: 'Provides default views for tests.'
|
||||
package: Testing
|
||||
# version: VERSION
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- views
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-03-03
|
||||
version: '8.x-1.1'
|
||||
core: '8.x'
|
||||
project: 'pathauto'
|
||||
datestamp: 1520092688
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\pathauto\Kernel;
|
||||
|
||||
use Drupal\Component\Serialization\PhpSerialize;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\KeyValueStore\KeyValueDatabaseFactory;
|
||||
use Drupal\pathauto\PathautoState;
|
||||
use Drupal\pathauto\Tests\PathautoTestHelperTrait;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\pathauto_string_id_test\Entity\PathautoStringIdTest;
|
||||
|
||||
/**
|
||||
* Tests auto-aliasing of entities that use string IDs.
|
||||
*
|
||||
* @group pathauto
|
||||
*/
|
||||
class PathautoEntityWithStringIdTest extends KernelTestBase {
|
||||
|
||||
use PathautoTestHelperTrait;
|
||||
|
||||
/**
|
||||
* The alias type plugin instance.
|
||||
*
|
||||
* @var \Drupal\pathauto\AliasTypeBatchUpdateInterface
|
||||
*/
|
||||
protected $aliasType;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'system',
|
||||
'user',
|
||||
'field',
|
||||
'token',
|
||||
'path',
|
||||
'pathauto',
|
||||
'pathauto_string_id_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register(ContainerBuilder $container) {
|
||||
parent::register($container);
|
||||
// Kernel tests are using the 'keyvalue.memory' store but we want to test
|
||||
// against the 'keyvalue.database'.
|
||||
$container
|
||||
->register('keyvalue.database', KeyValueDatabaseFactory::class)
|
||||
->addArgument(new PhpSerialize())
|
||||
->addArgument($container->get('database'))
|
||||
->addTag('persist');
|
||||
$container->setAlias('keyvalue', 'keyvalue.database');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('system', ['key_value']);
|
||||
$this->installConfig(['system']);
|
||||
$this->installEntitySchema('pathauto_string_id_test');
|
||||
$this->createPattern('pathauto_string_id_test', '/[pathauto_string_id_test:name]');
|
||||
/** @var \Drupal\pathauto\AliasTypeManager $alias_type_manager */
|
||||
$alias_type_manager = $this->container->get('plugin.manager.alias_type');
|
||||
$this->aliasType = $alias_type_manager->createInstance('canonical_entities:pathauto_string_id_test');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test aliasing entities with long string ID.
|
||||
*
|
||||
* @dataProvider entityWithStringIdProvider
|
||||
*
|
||||
* @param string|int $id
|
||||
* The entity ID
|
||||
* @param string $expected_key
|
||||
* The expected key for 'pathauto_state.*' collections.
|
||||
*/
|
||||
public function testEntityWithStringId($id, $expected_key) {
|
||||
$entity = PathautoStringIdTest::create([
|
||||
'id' => $id,
|
||||
'name' => $name = $this->randomMachineName(),
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
// Check that the path was generated.
|
||||
$this->assertEntityAlias($entity, "/$name");
|
||||
// Check that the path auto state was saved with the expected key.
|
||||
$value = \Drupal::keyValue('pathauto_state.pathauto_string_id_test')->get($expected_key);
|
||||
$this->assertEquals(PathautoState::CREATE, $value);
|
||||
|
||||
$context = [];
|
||||
// Batch delete uses the key-value store collection 'pathauto_state.*. We
|
||||
// test that after a bulk delete all aliases are removed. Running only once
|
||||
// the batch delete process is enough as the batch size is 100.
|
||||
$this->aliasType->batchDelete($context);
|
||||
|
||||
// Check that the paths were removed on batch delete.
|
||||
$this->assertNoEntityAliasExists($entity, "/$name");
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test cases for ::testEntityWithStringId().
|
||||
*
|
||||
* @see \Drupal\Tests\pathauto\Kernel\PathautoEntityWithStringIdTest::testEntityWithStringId()
|
||||
*/
|
||||
public function entityWithStringIdProvider() {
|
||||
return [
|
||||
'ascii with less or equal 128 chars' => [
|
||||
str_repeat('a', 128), str_repeat('a', 128)
|
||||
],
|
||||
'ascii with over 128 chars' => [
|
||||
str_repeat('a', 191), Crypt::hashBase64(str_repeat('a', 191))
|
||||
],
|
||||
'non-ascii with less or equal 128 chars' => [
|
||||
str_repeat('社', 128), Crypt::hashBase64(str_repeat('社', 128))
|
||||
],
|
||||
'non-ascii with over 128 chars' => [
|
||||
str_repeat('社', 191), Crypt::hashBase64(str_repeat('社', 191))
|
||||
],
|
||||
'simulating an integer id' => [
|
||||
123, '123'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,588 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\pathauto\Kernel;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\pathauto\PathautoGeneratorInterface;
|
||||
use Drupal\pathauto\PathautoState;
|
||||
use Drupal\pathauto\Tests\PathautoTestHelperTrait;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* Unit tests for Pathauto functions.
|
||||
*
|
||||
* @group pathauto
|
||||
*/
|
||||
class PathautoKernelTest extends KernelTestBase {
|
||||
|
||||
use PathautoTestHelperTrait;
|
||||
|
||||
public static $modules = array('system', 'field', 'text', 'user', 'node', 'path', 'pathauto', 'taxonomy', 'token', 'filter', 'ctools', 'language');
|
||||
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* @var \Drupal\pathauto\PathautoPatternInterface
|
||||
*/
|
||||
protected $nodePattern;
|
||||
|
||||
/**
|
||||
* @var \Drupal\pathauto\PathautoPatternInterface
|
||||
*/
|
||||
protected $userPattern;
|
||||
|
||||
public function setUp() {
|
||||
parent::setup();
|
||||
|
||||
$this->installConfig(array('pathauto', 'taxonomy', 'system', 'node'));
|
||||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
|
||||
$this->installSchema('node', array('node_access'));
|
||||
$this->installSchema('system', array('url_alias', 'sequences', 'router'));
|
||||
|
||||
$type = NodeType::create(['type' => 'page']);
|
||||
$type->save();
|
||||
node_add_body_field($type);
|
||||
|
||||
$this->nodePattern = $this->createPattern('node', '/content/[node:title]');
|
||||
$this->userPattern = $this->createPattern('user', '/users/[user:name]');
|
||||
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
|
||||
$this->currentUser = User::create(array('name' => $this->randomMachineName()));
|
||||
$this->currentUser->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test _pathauto_get_schema_alias_maxlength().
|
||||
*/
|
||||
public function testGetSchemaAliasMaxLength() {
|
||||
$this->assertIdentical(\Drupal::service('pathauto.alias_storage_helper')->getAliasSchemaMaxlength(), 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test pathauto_pattern_load_by_entity().
|
||||
*/
|
||||
public function testPatternLoadByEntity() {
|
||||
$pattern = $this->createPattern('node', '/article/[node:title]', -1);
|
||||
$this->addBundleCondition($pattern, 'node', 'article');
|
||||
$pattern->save();
|
||||
|
||||
$pattern = $this->createPattern('node', '/article/en/[node:title]', -2);
|
||||
$this->addBundleCondition($pattern, 'node', 'article');
|
||||
$pattern->addSelectionCondition(
|
||||
[
|
||||
'id' => 'language',
|
||||
'langcodes' => [
|
||||
'en' => 'en',
|
||||
],
|
||||
'negate' => FALSE,
|
||||
'context_mapping' => [
|
||||
'language' => 'node:langcode:language',
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
$pattern->addRelationship('node:langcode:language');
|
||||
$pattern->save();
|
||||
|
||||
$pattern = $this->createPattern('node', '/[node:title]', -1);
|
||||
$this->addBundleCondition($pattern, 'node', 'page');
|
||||
$pattern->save();
|
||||
|
||||
$tests = array(
|
||||
array(
|
||||
'entity' => 'node',
|
||||
'values' => [
|
||||
'title' => 'Article fr',
|
||||
'type' => 'article',
|
||||
'langcode' => 'fr',
|
||||
],
|
||||
'expected' => '/article/[node:title]',
|
||||
),
|
||||
array(
|
||||
'entity' => 'node',
|
||||
'values' => [
|
||||
'title' => 'Article en',
|
||||
'type' => 'article',
|
||||
'langcode' => 'en',
|
||||
],
|
||||
'expected' => '/article/en/[node:title]',
|
||||
),
|
||||
array(
|
||||
'entity' => 'node',
|
||||
'values' => [
|
||||
'title' => 'Article und',
|
||||
'type' => 'article',
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
],
|
||||
'expected' => '/article/[node:title]',
|
||||
),
|
||||
array(
|
||||
'entity' => 'node',
|
||||
'values' => [
|
||||
'title' => 'Page',
|
||||
'type' => 'page',
|
||||
],
|
||||
'expected' => '/[node:title]',
|
||||
),
|
||||
array(
|
||||
'entity' => 'user',
|
||||
'values' => [
|
||||
'name' => 'User',
|
||||
],
|
||||
'expected' => '/users/[user:name]',
|
||||
),
|
||||
);
|
||||
foreach ($tests as $test) {
|
||||
$entity = \Drupal::entityTypeManager()->getStorage($test['entity'])->create($test['values']);
|
||||
$entity->save();
|
||||
$actual = \Drupal::service('pathauto.generator')->getPatternByEntity($entity);
|
||||
$this->assertIdentical($actual->getPattern(), $test['expected'], t("Correct pattern returned for @entity_type with @values", array(
|
||||
'@entity' => $test['entity'],
|
||||
'@values' => print_r($test['values'], TRUE),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test potential conflicts with the same alias in different languages.
|
||||
*/
|
||||
public function testSameTitleDifferentLanguages() {
|
||||
// Create two English articles with the same title.
|
||||
$edit = [
|
||||
'title' => 'Sample page',
|
||||
'type' => 'page',
|
||||
'langcode' => 'en',
|
||||
];
|
||||
$node1 = $this->drupalCreateNode($edit);
|
||||
$this->assertEntityAlias($node1, '/content/sample-page', 'en');
|
||||
|
||||
$node2 = $this->drupalCreateNode($edit);
|
||||
$this->assertEntityAlias($node2, '/content/sample-page-0', 'en');
|
||||
|
||||
// Now, create a French article with the same title, and verify that it gets
|
||||
// the basic alias with the correct langcode.
|
||||
$edit['langcode'] = 'fr';
|
||||
$node3 = $this->drupalCreateNode($edit);
|
||||
$this->assertEntityAlias($node3, '/content/sample-page', 'fr');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test pathauto_cleanstring().
|
||||
*/
|
||||
public function testCleanString() {
|
||||
|
||||
// Test with default settings defined in pathauto.settings.yml.
|
||||
$this->installConfig(array('pathauto'));
|
||||
\Drupal::service('pathauto.generator')->resetCaches();
|
||||
|
||||
$tests = array();
|
||||
|
||||
// Test the 'ignored words' removal.
|
||||
$tests['this'] = 'this';
|
||||
$tests['this with that'] = 'this-with-that';
|
||||
$tests['this thing with that thing'] = 'thing-thing';
|
||||
|
||||
// Test 'ignored words' removal and duplicate separator removal.
|
||||
$tests[' - Pathauto is the greatest - module ever - '] = 'pathauto-greatest-module-ever';
|
||||
|
||||
// Test length truncation and lowering of strings.
|
||||
$long_string = $this->randomMachineName(120);
|
||||
$tests[$long_string] = strtolower(substr($long_string, 0, 100));
|
||||
|
||||
// Test that HTML tags are removed.
|
||||
$tests['This <span class="text">text</span> has <br /><a href="http://example.com"><strong>HTML tags</strong></a>.'] = 'text-has-html-tags';
|
||||
$tests[Html::escape('This <span class="text">text</span> has <br /><a href="http://example.com"><strong>HTML tags</strong></a>.')] = 'text-has-html-tags';
|
||||
|
||||
// Transliteration.
|
||||
$tests['ľščťžýáíéňô'] = 'lsctzyaieno';
|
||||
|
||||
foreach ($tests as $input => $expected) {
|
||||
$output = \Drupal::service('pathauto.alias_cleaner')->cleanString($input);
|
||||
$this->assertEqual($output, $expected, t("Drupal::service('pathauto.alias_cleaner')->cleanString('@input') expected '@expected', actual '@output'", array(
|
||||
'@input' => $input,
|
||||
'@expected' => $expected,
|
||||
'@output' => $output,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test pathauto_clean_alias().
|
||||
*/
|
||||
public function testCleanAlias() {
|
||||
$tests = array();
|
||||
$tests['one/two/three'] = '/one/two/three';
|
||||
$tests['/one/two/three/'] = '/one/two/three';
|
||||
$tests['one//two///three'] = '/one/two/three';
|
||||
$tests['one/two--three/-/--/-/--/four---five'] = '/one/two-three/four-five';
|
||||
$tests['one/-//three--/four'] = '/one/three/four';
|
||||
|
||||
foreach ($tests as $input => $expected) {
|
||||
$output = \Drupal::service('pathauto.alias_cleaner')->cleanAlias($input);
|
||||
$this->assertEqual($output, $expected, t("Drupal::service('pathauto.generator')->cleanAlias('@input') expected '@expected', actual '@output'", array(
|
||||
'@input' => $input,
|
||||
'@expected' => $expected,
|
||||
'@output' => $output,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test pathauto_path_delete_multiple().
|
||||
*/
|
||||
public function testPathDeleteMultiple() {
|
||||
$this->saveAlias('/node/1', '/node-1-alias');
|
||||
$this->saveAlias('/node/1/view', '/node-1-alias/view');
|
||||
$this->saveAlias('/node/1', '/node-1-alias-en', 'en');
|
||||
$this->saveAlias('/node/1', '/node-1-alias-fr', 'fr');
|
||||
$this->saveAlias('/node/2', '/node-2-alias');
|
||||
$this->saveAlias('/node/10', '/node-10-alias');
|
||||
|
||||
\Drupal::service('pathauto.alias_storage_helper')->deleteBySourcePrefix('/node/1');
|
||||
$this->assertNoAliasExists(array('source' => "/node/1"));
|
||||
$this->assertNoAliasExists(array('source' => "/node/1/view"));
|
||||
$this->assertAliasExists(array('source' => "/node/2"));
|
||||
$this->assertAliasExists(array('source' => "/node/10"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the different update actions in \Drupal::service('pathauto.generator')->createEntityAlias().
|
||||
*/
|
||||
public function testUpdateActions() {
|
||||
$config = $this->config('pathauto.settings');
|
||||
|
||||
// Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'insert'.
|
||||
$config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW);
|
||||
$config->save();
|
||||
$node = $this->drupalCreateNode(array('title' => 'First title'));
|
||||
$this->assertEntityAlias($node, '/content/first-title');
|
||||
|
||||
$node->path->pathauto = PathautoState::CREATE;
|
||||
|
||||
// Default action is PATHAUTO_UPDATE_ACTION_DELETE.
|
||||
$config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_DELETE);
|
||||
$config->save();
|
||||
$node->setTitle('Second title');
|
||||
$node->save();
|
||||
$this->assertEntityAlias($node, '/content/second-title');
|
||||
$this->assertNoAliasExists(array('alias' => '/content/first-title'));
|
||||
|
||||
// Test PATHAUTO_UPDATE_ACTION_LEAVE
|
||||
$config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_LEAVE);
|
||||
$config->save();
|
||||
$node->setTitle('Third title');
|
||||
$node->save();
|
||||
$this->assertEntityAlias($node, '/content/third-title');
|
||||
$this->assertAliasExists(array('source' => '/' . $node->toUrl()->getInternalPath(), 'alias' => '/content/second-title'));
|
||||
|
||||
$config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_DELETE);
|
||||
$config->save();
|
||||
$node->setTitle('Fourth title');
|
||||
$node->save();
|
||||
$this->assertEntityAlias($node, '/content/fourth-title');
|
||||
$this->assertNoAliasExists(array('alias' => '/content/third-title'));
|
||||
// The older second alias is not deleted yet.
|
||||
$older_path = $this->assertAliasExists(array('source' => '/' . $node->toUrl()->getInternalPath(), 'alias' => '/content/second-title'));
|
||||
\Drupal::service('path.alias_storage')->delete($older_path);
|
||||
|
||||
$config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW);
|
||||
$config->save();
|
||||
$node->setTitle('Fifth title');
|
||||
$node->save();
|
||||
$this->assertEntityAlias($node, '/content/fourth-title');
|
||||
$this->assertNoAliasExists(array('alias' => '/content/fifth-title'));
|
||||
|
||||
// Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'update'.
|
||||
$this->deleteAllAliases();
|
||||
$node->save();
|
||||
$this->assertEntityAlias($node, '/content/fifth-title');
|
||||
|
||||
// Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'bulkupdate'.
|
||||
$this->deleteAllAliases();
|
||||
$node->setTitle('Sixth title');
|
||||
\Drupal::service('pathauto.generator')->updateEntityAlias($node, 'bulkupdate');
|
||||
$this->assertEntityAlias($node, '/content/sixth-title');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that \Drupal::service('pathauto.generator')->createEntityAlias() will not create an alias for a pattern
|
||||
* that does not get any tokens replaced.
|
||||
*/
|
||||
public function testNoTokensNoAlias() {
|
||||
$this->installConfig(['filter']);
|
||||
$this->nodePattern
|
||||
->setPattern('/content/[node:body]')
|
||||
->save();
|
||||
|
||||
$node = $this->drupalCreateNode();
|
||||
$this->assertNoEntityAliasExists($node);
|
||||
|
||||
$node->body->value = 'hello';
|
||||
$node->save();
|
||||
$this->assertEntityAlias($node, '/content/hello');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the handling of path vs non-path tokens in pathauto_clean_token_values().
|
||||
*/
|
||||
public function testPathTokens() {
|
||||
$this->createPattern('taxonomy_term', '/[term:parent:url:path]/[term:name]');
|
||||
|
||||
$vocab = $this->addVocabulary();
|
||||
|
||||
$term1 = $this->addTerm($vocab, array('name' => 'Parent term'));
|
||||
$this->assertEntityAlias($term1, '/parent-term');
|
||||
|
||||
$term2 = $this->addTerm($vocab, array('name' => 'Child term', 'parent' => $term1->id()));
|
||||
$this->assertEntityAlias($term2, '/parent-term/child-term');
|
||||
|
||||
$this->saveEntityAlias($term1, '/My Crazy/Alias/');
|
||||
$term2->save();
|
||||
$this->assertEntityAlias($term2, '/My Crazy/Alias/child-term');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test using fields for path structures.
|
||||
*/
|
||||
function testParentChildPathTokens() {
|
||||
// First create a field which will be used to create the path. It must
|
||||
// begin with a letter.
|
||||
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
|
||||
Vocabulary::create(['vid' => 'tags'])->save();
|
||||
|
||||
$fieldname = 'a' . Unicode::strtolower($this->randomMachineName());
|
||||
$field_storage = FieldStorageConfig::create(['entity_type' => 'taxonomy_term', 'field_name' => $fieldname, 'type' => 'string']);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create(['field_storage' => $field_storage, 'bundle' => 'tags']);
|
||||
$field->save();
|
||||
|
||||
$display = entity_get_display('taxonomy_term', 'tags', 'default');
|
||||
$display->setComponent($fieldname, ['type' => 'string']);
|
||||
$display->save();
|
||||
|
||||
// Make the path pattern of a field use the value of this field appended
|
||||
// to the parent taxonomy term's pattern if there is one.
|
||||
$this->createPattern('taxonomy_term', '/[term:parents:join-path]/[term:' . $fieldname . ']');
|
||||
|
||||
// Start by creating a parent term.
|
||||
$parent = Term::create(['vid' => 'tags', $fieldname => $this->randomMachineName(), 'name' => $this->randomMachineName()]);
|
||||
$parent->save();
|
||||
|
||||
// Create the child term.
|
||||
$child = Term::create(['vid' => 'tags', $fieldname => $this->randomMachineName(), 'parent' => $parent, 'name' => $this->randomMachineName()]);
|
||||
$child->save();
|
||||
$this->assertEntityAlias($child, '/' . Unicode::strtolower($parent->getName() . '/' . $child->$fieldname->value));
|
||||
|
||||
// Re-saving the parent term should not modify the child term's alias.
|
||||
$parent->save();
|
||||
$this->assertEntityAlias($child, '/' . Unicode::strtolower($parent->getName() . '/' . $child->$fieldname->value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests aliases on taxonomy terms.
|
||||
*/
|
||||
public function testTaxonomyPattern() {
|
||||
// Create a vocabulary and test that it's pattern variable works.
|
||||
$vocab = $this->addVocabulary(array('vid' => 'name'));
|
||||
$this->createPattern('taxonomy_term', 'base');
|
||||
$pattern = $this->createPattern('taxonomy_term', 'bundle', -1);
|
||||
$this->addBundleCondition($pattern, 'taxonomy_term', 'name');
|
||||
$pattern->save();
|
||||
$this->assertEntityPattern('taxonomy_term', 'name', Language::LANGCODE_NOT_SPECIFIED, 'bundle');
|
||||
}
|
||||
|
||||
function testNoExistingPathAliases() {
|
||||
$this->config('pathauto.settings')
|
||||
->set('punctuation.period', PathautoGeneratorInterface::PUNCTUATION_DO_NOTHING)
|
||||
->save();
|
||||
|
||||
$this->nodePattern
|
||||
->setPattern('[node:title]')
|
||||
->save();
|
||||
|
||||
// Check that Pathauto does not create an alias of '/admin'.
|
||||
$node = $this->drupalCreateNode(array('title' => 'Admin', 'type' => 'page'));
|
||||
$this->assertEntityAlias($node, '/admin-0');
|
||||
|
||||
// Check that Pathauto does not create an alias of '/modules'.
|
||||
$node->setTitle('Modules');
|
||||
$node->save();
|
||||
$this->assertEntityAlias($node, '/modules-0');
|
||||
|
||||
// Check that Pathauto does not create an alias of '/index.php'.
|
||||
$node->setTitle('index.php');
|
||||
$node->save();
|
||||
$this->assertEntityAlias($node, '/index.php-0');
|
||||
|
||||
// Check that a safe value gets an automatic alias. This is also a control
|
||||
// to ensure the above tests work properly.
|
||||
$node->setTitle('Safe value');
|
||||
$node->save();
|
||||
$this->assertEntityAlias($node, '/safe-value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test programmatic entity creation for aliases.
|
||||
*/
|
||||
function testProgrammaticEntityCreation() {
|
||||
$this->createPattern('taxonomy_term', '/[term:vocabulary]/[term:name]');
|
||||
$node = $this->drupalCreateNode(array('title' => 'Test node', 'path' => array('pathauto' => TRUE)));
|
||||
$this->assertEntityAlias($node, '/content/test-node');
|
||||
|
||||
$vocabulary = $this->addVocabulary(array('name' => 'Tags'));
|
||||
$term = $this->addTerm($vocabulary, array('name' => 'Test term', 'path' => array('pathauto' => TRUE)));
|
||||
$this->assertEntityAlias($term, '/tags/test-term');
|
||||
|
||||
$edit['name'] = 'Test user';
|
||||
$edit['mail'] = 'test-user@example.com';
|
||||
$edit['pass'] = user_password();
|
||||
$edit['path'] = array('pathauto' => TRUE);
|
||||
$edit['status'] = 1;
|
||||
$account = User::create($edit);
|
||||
$account->save();
|
||||
$this->assertEntityAlias($account, '/users/test-user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests word safe alias truncating.
|
||||
*/
|
||||
function testPathAliasUniquifyWordsafe() {
|
||||
$this->config('pathauto.settings')
|
||||
->set('max_length', 26)
|
||||
->save();
|
||||
|
||||
$node_1 = $this->drupalCreateNode(array('title' => 'thequick brownfox jumpedover thelazydog', 'type' => 'page'));
|
||||
$node_2 = $this->drupalCreateNode(array('title' => 'thequick brownfox jumpedover thelazydog', 'type' => 'page'));
|
||||
|
||||
// Check that alias uniquifying is truncating with $wordsafe param set to
|
||||
// TRUE.
|
||||
// If it doesn't path alias result would be content/thequick-brownf-0
|
||||
$this->assertEntityAlias($node_1, '/content/thequick-brownfox');
|
||||
$this->assertEntityAlias($node_2, '/content/thequick-0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if aliases are (not) generated with enabled/disabled patterns.
|
||||
*/
|
||||
function testPatternStatus() {
|
||||
// Create a node to get an alias for.
|
||||
$title = 'Pattern enabled';
|
||||
$alias = '/content/pattern-enabled';
|
||||
$node1 = $this->drupalCreateNode(['title' => $title, 'type' => 'page']);
|
||||
$this->assertEntityAlias($node1, $alias);
|
||||
|
||||
// Disable the pattern, save the node again and make sure the alias is still
|
||||
// working.
|
||||
$this->nodePattern->setStatus(FALSE)->save();
|
||||
|
||||
$node1->save();
|
||||
$this->assertEntityAlias($node1, $alias);
|
||||
|
||||
// Create a new node with disabled pattern and make sure there is no new
|
||||
// alias created.
|
||||
$title = 'Pattern disabled';
|
||||
$node2 = $this->drupalCreateNode(['title' => $title, 'type' => 'page']);
|
||||
$this->assertNoEntityAlias($node2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that enabled entity types genrates the necessary fields and plugins.
|
||||
*/
|
||||
public function testSettingChangeInvalidatesCache() {
|
||||
|
||||
$this->installConfig(['pathauto']);
|
||||
|
||||
$this->enableModules(['entity_test']);
|
||||
|
||||
$definitions = \Drupal::service('plugin.manager.alias_type')->getDefinitions();
|
||||
$this->assertFalse(isset($definitions['canonical_entities:entity_test']));
|
||||
|
||||
$fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('entity_test');
|
||||
$this->assertFalse(isset($fields['path']));
|
||||
|
||||
$this->config('pathauto.settings')
|
||||
->set('enabled_entity_types', ['user', 'entity_test'])
|
||||
->save();
|
||||
|
||||
$definitions = \Drupal::service('plugin.manager.alias_type')->getDefinitions();
|
||||
$this->assertTrue(isset($definitions['canonical_entities:entity_test']));
|
||||
|
||||
$fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('entity_test');
|
||||
$this->assertTrue(isset($fields['path']));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that aliases are only generated for default revisions.
|
||||
*/
|
||||
public function testDefaultRevision() {
|
||||
$node1 = $this->drupalCreateNode(['title' => 'Default revision', 'type' => 'page']);
|
||||
$this->assertEntityAlias($node1, '/content/default-revision');
|
||||
|
||||
$node1->setNewRevision(TRUE);
|
||||
$node1->isDefaultRevision(FALSE);
|
||||
$node1->setTitle('New non-default-revision');
|
||||
$node1->save();
|
||||
|
||||
$this->assertEntityAlias($node1, '/content/default-revision');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the pathauto state property gets set to CREATED for new nodes.
|
||||
*
|
||||
* In some cases, this can trigger $node->path to be set up with no default
|
||||
* value for the pathauto property.
|
||||
*/
|
||||
public function testCreateNodeWhileAccessingPath() {
|
||||
$node = Node::create([
|
||||
'type' => 'article',
|
||||
'title' => 'TestAlias',
|
||||
]);
|
||||
$node->path->langcode;
|
||||
$node->save();
|
||||
$this->assertEntityAlias($node, '/content/testalias');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a node programmatically.
|
||||
*
|
||||
* @param array $settings
|
||||
* The array of values for the node.
|
||||
*
|
||||
* @return \Drupal\node\Entity\Node
|
||||
* The created node.
|
||||
*/
|
||||
protected function drupalCreateNode(array $settings = array()) {
|
||||
// Populate defaults array.
|
||||
$settings += array(
|
||||
'title' => $this->randomMachineName(8),
|
||||
'type' => 'page',
|
||||
);
|
||||
|
||||
$node = Node::create($settings);
|
||||
$node->save();
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\pathauto\Kernel;
|
||||
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests tokens provided by Pathauto.
|
||||
*
|
||||
* @group pathauto
|
||||
*/
|
||||
class PathautoTokenTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system', 'token', 'pathauto');
|
||||
|
||||
public function testPathautoTokens() {
|
||||
|
||||
$this->installConfig(array('pathauto'));
|
||||
|
||||
$array = array(
|
||||
'test first arg',
|
||||
'The Array / value',
|
||||
);
|
||||
|
||||
$tokens = array(
|
||||
'join-path' => 'test-first-arg/array-value',
|
||||
);
|
||||
$data['array'] = $array;
|
||||
$replacements = $this->assertTokens('array', $data, $tokens);
|
||||
|
||||
// Ensure that the cleanTokenValues() method does not alter this token value.
|
||||
/* @var \Drupal\pathauto\AliasCleanerInterface $alias_cleaner */
|
||||
$alias_cleaner = \Drupal::service('pathauto.alias_cleaner');
|
||||
$alias_cleaner->cleanTokenValues($replacements, $data, array());
|
||||
$this->assertEqual($replacements['[array:join-path]'], 'test-first-arg/array-value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Function copied from TokenTestHelper::assertTokens().
|
||||
*/
|
||||
public function assertTokens($type, array $data, array $tokens, array $options = array()) {
|
||||
$input = $this->mapTokenNames($type, array_keys($tokens));
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$replacements = \Drupal::token()->generate($type, $input, $data, $options, $bubbleable_metadata);
|
||||
foreach ($tokens as $name => $expected) {
|
||||
$token = $input[$name];
|
||||
if (!isset($expected)) {
|
||||
$this->assertTrue(!isset($values[$token]), t("Token value for @token was not generated.", array('@type' => $type, '@token' => $token)));
|
||||
}
|
||||
elseif (!isset($replacements[$token])) {
|
||||
$this->fail(t("Token value for @token was not generated.", array('@type' => $type, '@token' => $token)));
|
||||
}
|
||||
elseif (!empty($options['regex'])) {
|
||||
$this->assertTrue(preg_match('/^' . $expected . '$/', $replacements[$token]), t("Token value for @token was '@actual', matching regular expression pattern '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected)));
|
||||
}
|
||||
else {
|
||||
$this->assertIdentical($replacements[$token], $expected, t("Token value for @token was '@actual', expected value '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected)));
|
||||
}
|
||||
}
|
||||
|
||||
return $replacements;
|
||||
}
|
||||
|
||||
public function mapTokenNames($type, array $tokens = array()) {
|
||||
$return = array();
|
||||
foreach ($tokens as $token) {
|
||||
$return[$token] = "[$type:$token]";
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\pathauto\Unit {
|
||||
|
||||
use Drupal\pathauto\VerboseMessenger;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\pathauto\VerboseMessenger
|
||||
* @group pathauto
|
||||
*/
|
||||
class VerboseMessengerTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The messenger under test.
|
||||
*
|
||||
* @var \Drupal\pathauto\VerboseMessenger
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
$config_factory = $this->getConfigFactoryStub(array('pathauto.settings' => array('verbose' => TRUE)));
|
||||
$account = $this->getMock('\Drupal\Core\Session\AccountInterface');
|
||||
$account->expects($this->once())
|
||||
->method('hasPermission')
|
||||
->withAnyParameters()
|
||||
->willReturn(TRUE);
|
||||
|
||||
$this->messenger = new VerboseMessenger($config_factory, $account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests add messages.
|
||||
* @covers ::addMessage
|
||||
*/
|
||||
public function testAddMessage() {
|
||||
$this->assertTrue($this->messenger->addMessage("Test message"), "The message was added");
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addMessage
|
||||
*/
|
||||
public function testDoNotAddMessageWhileBulkupdate() {
|
||||
$this->assertFalse($this->messenger->addMessage("Test message", "bulkupdate"), "The message was NOT added");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
namespace {
|
||||
// @todo Delete after https://drupal.org/node/1858196 is in.
|
||||
if (!function_exists('drupal_set_message')) {
|
||||
function drupal_set_message() {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "drupal/ctools",
|
||||
"description": "Provides a number of utility and helper APIs for Drupal developers and site builders.",
|
||||
"type": "drupal-module",
|
||||
"license": "GPL-2.0+",
|
||||
"minimum-stability": "dev",
|
||||
"homepage": "https://www.drupal.org/project/ctools",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kris Vanderwater (EclipseGc)",
|
||||
"homepage": "https://www.drupal.org/u/eclipsegc",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Jakob Perry (japerry)",
|
||||
"homepage": "https://www.drupal.org/u/japerry",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Tim Plunkett (tim.plunkett)",
|
||||
"homepage": "https://www.drupal.org/u/timplunkett",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "James Gilliland (neclimdul)",
|
||||
"homepage": "https://www.drupal.org/u/neclimdul",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Daniel Wehner (dawehner)",
|
||||
"homepage": "https://www.drupal.org/u/dawehner",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://www.drupal.org/project/issues/ctools",
|
||||
"source": "http://cgit.drupalcode.org/ctools"
|
||||
},
|
||||
"require": {}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
ctools.context:
|
||||
type: mapping
|
||||
label: Context
|
||||
mapping:
|
||||
label:
|
||||
type: label
|
||||
label: 'Label of the context'
|
||||
type:
|
||||
type: string
|
||||
label: 'Context type'
|
||||
description:
|
||||
type: string
|
||||
label: 'Description of the context'
|
||||
value:
|
||||
type: string
|
||||
label: 'Context value'
|
||||
|
||||
ctools.relationship:
|
||||
type: mapping
|
||||
label: 'Relationship'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'ID'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
|
||||
condition.plugin.entity_bundle:*:
|
||||
type: condition.plugin
|
||||
mapping:
|
||||
bundles:
|
||||
type: sequence
|
||||
sequence:
|
||||
type: string
|
||||
|
||||
ctools.block_plugin.*:
|
||||
type: block.settings.[id]
|
||||
mapping:
|
||||
region:
|
||||
type: string
|
||||
label: 'Region'
|
||||
weight:
|
||||
type: integer
|
||||
label: 'Weight'
|
||||
uuid:
|
||||
type: string
|
||||
label: 'UUID'
|
||||
context_mapping:
|
||||
type: sequence
|
||||
label: 'Context assignments'
|
||||
sequence:
|
||||
- type: string
|
||||
|
||||
ctools.block_display_variant:
|
||||
type: display_variant.plugin
|
||||
label: 'Block display variant'
|
||||
mapping:
|
||||
blocks:
|
||||
type: sequence
|
||||
label: 'Blocks'
|
||||
sequence:
|
||||
- type: ctools.block_plugin.[id]
|
||||
@@ -0,0 +1,11 @@
|
||||
name: Chaos tools
|
||||
type: module
|
||||
description: 'Provides a number of utility and helper APIs for Drupal developers and site builders.'
|
||||
package: Chaos tool suite
|
||||
# core: 8.x
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-04-28
|
||||
version: '8.x-3.0'
|
||||
core: '8.x'
|
||||
project: 'ctools'
|
||||
datestamp: 1493401747
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
function ctools_theme($existing, $type, $theme, $path) {
|
||||
return [
|
||||
'ctools_wizard_trail' => [
|
||||
'variables' => [
|
||||
'wizard' => NULL,
|
||||
'cached_values' => [],
|
||||
'trail' => [],
|
||||
'divider' => ' » ',
|
||||
'step' => NULL,
|
||||
],
|
||||
],
|
||||
'ctools_wizard_trail_links' => [
|
||||
'variables' => [
|
||||
'wizard' => NULL,
|
||||
'cached_values' => [],
|
||||
'trail' => [],
|
||||
'divider' => ' » ',
|
||||
'step' => NULL,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function template_preprocess_ctools_wizard_trail(&$variables) {
|
||||
/** @var $wizard \Drupal\ctools\Wizard\FormWizardInterface|\Drupal\ctools\Wizard\EntityFormWizardInterface */
|
||||
$wizard = $variables['wizard'];
|
||||
$cached_values = $variables['cached_values'];
|
||||
$trail = $variables['trail'];
|
||||
$variables['step'] = $wizard->getStep($cached_values);
|
||||
foreach ($wizard->getOperations($cached_values) as $step => $operation) {
|
||||
$trail[$step] = !empty($operation['title']) ? $operation['title'] : '';
|
||||
}
|
||||
$variables['trail'] = $trail;
|
||||
}
|
||||
|
||||
function template_preprocess_ctools_wizard_trail_links(&$variables) {
|
||||
/** @var $wizard \Drupal\ctools\Wizard\FormWizardInterface|\Drupal\ctools\Wizard\EntityFormWizardInterface */
|
||||
$wizard = $variables['wizard'];
|
||||
$cached_values = $variables['cached_values'];
|
||||
$trail = $variables['trail'];
|
||||
$variables['step'] = $wizard->getStep($cached_values);
|
||||
foreach ($wizard->getOperations($cached_values) as $step => $operation) {
|
||||
$parameters = $wizard->getNextParameters($cached_values);
|
||||
// Override step to be the step we want.
|
||||
$parameters['step'] = $step;
|
||||
$trail[$step] = [
|
||||
'title' => !empty($operation['title']) ? $operation['title'] : '',
|
||||
'url' => new \Drupal\Core\Url($wizard->getRouteName(), $parameters),
|
||||
];
|
||||
}
|
||||
$variables['trail'] = $trail;
|
||||
}
|
||||
|
||||
function ctools_condition_info_alter(&$definitions) {
|
||||
// If the node_type's class is unaltered, use a custom ctools implementation.
|
||||
if (isset($definitions['node_type']) && $definitions['node_type']['class'] == 'Drupal\node\Plugin\Condition\NodeType') {
|
||||
$definitions['node_type']['class'] = 'Drupal\ctools\Plugin\Condition\NodeType';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
services:
|
||||
ctools.wizard.form:
|
||||
class: Drupal\ctools\Controller\WizardFormController
|
||||
arguments: ['@controller_resolver', '@form_builder', '@ctools.wizard.factory']
|
||||
ctools.wizard.entity.form:
|
||||
class: Drupal\ctools\Controller\WizardEntityFormController
|
||||
arguments: ['@controller_resolver', '@form_builder', '@ctools.wizard.factory', '@entity.manager']
|
||||
ctools.wizard_enhancer:
|
||||
class: Drupal\ctools\Routing\Enhancer\WizardEnhancer
|
||||
tags:
|
||||
- { name: route_enhancer }
|
||||
ctools.wizard.factory:
|
||||
class: Drupal\ctools\Wizard\WizardFactory
|
||||
arguments: ['@form_builder', '@event_dispatcher']
|
||||
ctools.paramconverter.tempstore:
|
||||
class: Drupal\ctools\ParamConverter\TempstoreConverter
|
||||
arguments: ['@user.shared_tempstore', '@entity_type.manager']
|
||||
tags:
|
||||
- { name: paramconverter }
|
||||
ctools.typed_data.resolver:
|
||||
class: Drupal\ctools\TypedDataResolver
|
||||
arguments: ['@typed_data_manager', '@string_translation']
|
||||
ctools.access:
|
||||
class: Drupal\ctools\Access\TempstoreAccess
|
||||
arguments: ['@user.shared_tempstore']
|
||||
tags:
|
||||
- { name: access_check, applies_to: _ctools_access }
|
||||
plugin.manager.ctools.relationship:
|
||||
class: Drupal\ctools\Plugin\RelationshipManager
|
||||
parent: default_plugin_manager
|
||||
ctools.context_mapper:
|
||||
class: Drupal\ctools\ContextMapper
|
||||
arguments: ['@entity.repository']
|
||||
ctools.serializable.tempstore.factory:
|
||||
class: Drupal\ctools\SerializableTempstoreFactory
|
||||
arguments: ['@keyvalue.expirable', '@lock', '@request_stack', '%user.tempstore.expire%']
|
||||
tags:
|
||||
- { name: backend_overridable }
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
block.settings.entity_field:*:*:
|
||||
type: block_settings
|
||||
label: 'Entity field block'
|
||||
mapping:
|
||||
formatter:
|
||||
type: mapping
|
||||
label: 'Field formatter'
|
||||
mapping:
|
||||
type:
|
||||
type: string
|
||||
label: 'Format type machine name'
|
||||
weight:
|
||||
type: integer
|
||||
label: 'Weight'
|
||||
region:
|
||||
type: string
|
||||
label: 'Region'
|
||||
label:
|
||||
type: string
|
||||
label: 'Label setting machine name'
|
||||
settings:
|
||||
type: field.formatter.settings.[%parent.type]
|
||||
label: 'Settings'
|
||||
third_party_settings:
|
||||
type: sequence
|
||||
label: 'Third party settings'
|
||||
sequence:
|
||||
type: field.formatter.third_party.[%key]
|
||||
@@ -0,0 +1,14 @@
|
||||
name: Chaos tools blocks
|
||||
type: module
|
||||
description: 'Provides improvements to blocks that will one day be added to Drupal core.'
|
||||
package: Chaos tool suite (Experimental)
|
||||
# version: 3.x
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- ctools
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-04-28
|
||||
version: '8.x-3.0'
|
||||
core: '8.x'
|
||||
project: 'ctools'
|
||||
datestamp: 1493401747
|
||||
+376
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ctools_block\Plugin\Block;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
use Drupal\Core\Field\FormatterPluginManager;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\ContextAwarePluginInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a block to a field on an entity.
|
||||
*
|
||||
* @Block(
|
||||
* id = "entity_field",
|
||||
* deriver = "Drupal\ctools_block\Plugin\Deriver\EntityFieldDeriver",
|
||||
* )
|
||||
*/
|
||||
class EntityField extends BlockBase implements ContextAwarePluginInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The entity field manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* The field type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
|
||||
*/
|
||||
protected $fieldTypeManager;
|
||||
|
||||
/**
|
||||
* The formatter manager.
|
||||
*
|
||||
* @var \Drupal\Core\Field\FormatterPluginManager
|
||||
*/
|
||||
protected $formatterManager;
|
||||
|
||||
/**
|
||||
* The entity type id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId;
|
||||
|
||||
/**
|
||||
* The field name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* The field definition.
|
||||
*
|
||||
* @var \Drupal\Core\Field\FieldDefinitionInterface
|
||||
*/
|
||||
protected $fieldDefinition;
|
||||
|
||||
/**
|
||||
* The field storage definition.
|
||||
*
|
||||
* @var \Drupal\Core\Field\FieldStorageDefinitionInterface
|
||||
*/
|
||||
protected $fieldStorageDefinition;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityField.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
|
||||
* The entity field manager.
|
||||
* @param \Drupal\Core\Field\FormatterPluginManager $formatter_manager
|
||||
* The formatter manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, FieldTypePluginManagerInterface $field_type_manager, FormatterPluginManager $formatter_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
$this->fieldTypeManager = $field_type_manager;
|
||||
$this->formatterManager = $formatter_manager;
|
||||
|
||||
// Get the entity type and field name from the plugin id.
|
||||
list (, $entity_type_id, $field_name) = explode(':', $plugin_id);
|
||||
$this->entityTypeId = $entity_type_id;
|
||||
$this->fieldName = $field_name;
|
||||
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_field.manager'),
|
||||
$container->get('plugin.manager.field.field_type'),
|
||||
$container->get('plugin.manager.field.formatter')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
/** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
|
||||
$entity = $this->getContextValue('entity');
|
||||
$build = [];
|
||||
/** @var \Drupal\Core\Field\FieldItemListInterface $field */
|
||||
$field = $entity->{$this->fieldName};
|
||||
$display_settings = $this->getConfiguration()['formatter'];
|
||||
$build['field'] = $field->view($display_settings);
|
||||
|
||||
// Set the cache data appropriately.
|
||||
$build['#cache']['contexts'] = $this->getCacheContexts();
|
||||
$build['#cache']['tags'] = $this->getCacheTags();
|
||||
$build['#cache']['max-age'] = $this->getCacheMaxAge();
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockAccess(AccountInterface $account) {
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $entity */
|
||||
$entity = $this->getContextValue('entity');
|
||||
// Make sure we have access to the entity.
|
||||
$access = $entity->access('view', $account, TRUE);
|
||||
if ($access->isAllowed()) {
|
||||
// Check that the entity in question has this field.
|
||||
if ($entity instanceof FieldableEntityInterface && $entity->hasField($this->fieldName)) {
|
||||
// Check field access.
|
||||
$field_access = $this->entityTypeManager
|
||||
->getAccessControlHandler($this->entityTypeId)
|
||||
->fieldAccess('view', $this->getFieldDefinition(), $account);
|
||||
|
||||
if ($field_access) {
|
||||
// Build a renderable array for the field.
|
||||
$build = $entity->get($this->fieldName)->view($this->configuration['formatter']);
|
||||
// If there are actual renderable children, grant access.
|
||||
if (Element::children($build)) {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Entity doesn't have this field, so access is denied.
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
// If we don't have access to the entity, return the forbidden result.
|
||||
return $access;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
$field_type_definition = $this->getFieldTypeDefinition();
|
||||
return [
|
||||
'formatter' => [
|
||||
'label' => 'above',
|
||||
'type' => $field_type_definition['default_formatter'] ?: '',
|
||||
'settings' => [],
|
||||
'third_party_settings' => [],
|
||||
'weight' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state) {
|
||||
$config = $this->getConfiguration();
|
||||
|
||||
$form['formatter_label'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Label'),
|
||||
'#options' => [
|
||||
'above' => $this->t('Above'),
|
||||
'inline' => $this->t('Inline'),
|
||||
'hidden' => '- ' . $this->t('Hidden') . ' -',
|
||||
'visually_hidden' => '- ' . $this->t('Visually Hidden') . ' -',
|
||||
],
|
||||
'#default_value' => $config['formatter']['label'],
|
||||
];
|
||||
|
||||
$form['formatter_type'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Formatter'),
|
||||
'#options' => $this->getFormatterOptions(),
|
||||
'#default_value' => $config['formatter']['type'],
|
||||
'#ajax' => [
|
||||
'callback' => [static::class, 'formatterSettingsAjaxCallback'],
|
||||
'wrapper' => 'formatter-settings-wrapper',
|
||||
'effect' => 'fade',
|
||||
],
|
||||
];
|
||||
|
||||
// Add the formatter settings to the form via AJAX.
|
||||
$form['#process'][] = [$this, 'formatterSettingsProcessCallback'];
|
||||
$form['formatter_settings_wrapper'] = [
|
||||
'#prefix' => '<div id="formatter-settings-wrapper">',
|
||||
'#suffix' => '</div>',
|
||||
];
|
||||
$form['formatter_settings_wrapper']['formatter_settings'] = [
|
||||
'#tree' => TRUE,
|
||||
// The settings from the formatter plugin will be added in the
|
||||
// ::formatterSettingsProcessCallback method.
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render API callback: builds the formatter settings elements.
|
||||
*/
|
||||
public function formatterSettingsProcessCallback(array &$element, FormStateInterface $form_state, array &$complete_form) {
|
||||
$config = $this->getConfiguration();
|
||||
$parents_base = $element['#parents'];
|
||||
$formatter_parent = array_merge($parents_base, ['formatter_type']);
|
||||
$formatter_settings_parent = array_merge($parents_base, ['formatter_settings']);
|
||||
|
||||
$settings_element = &$element['formatter_settings_wrapper']['formatter_settings'];
|
||||
|
||||
// Set the #parents on the formatter_settings so they end up as a peer to
|
||||
// formatter_type.
|
||||
$settings_element['#parents'] = $formatter_settings_parent;
|
||||
|
||||
// Get the formatter name in a way that works regardless of whether we're
|
||||
// getting the value via AJAX or not.
|
||||
$formatter_name = NestedArray::getValue($form_state->getUserInput(), $formatter_parent) ?: $element['formatter_type']['#default_value'];
|
||||
|
||||
// Place the formatter settings on the form if a formatter is selected.
|
||||
$formatter = $this->getFormatter($formatter_name, $form_state->getValue('formatter_label'), $form_state->getValue($formatter_settings_parent, $config['formatter']['settings']), $config['formatter']['third_party_settings']);
|
||||
$settings_element = array_merge($formatter->settingsForm($settings_element, $form_state), $settings_element);
|
||||
|
||||
// Store the array parents for our element so that we can use it to pull out
|
||||
// the formatter settings in our AJAX callback.
|
||||
$complete_form['#formatter_array_parents'] = $element['#array_parents'];
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render API callback: gets the layout settings elements.
|
||||
*/
|
||||
public static function formatterSettingsAjaxCallback(array $form, FormStateInterface $form_state) {
|
||||
$formatter_array_parents = $form['#formatter_array_parents'];
|
||||
return NestedArray::getValue($form, array_merge($formatter_array_parents, ['formatter_settings_wrapper']));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockSubmit($form, FormStateInterface $form_state) {
|
||||
$this->configuration['formatter']['label'] = $form_state->getValue('formatter_label');
|
||||
$this->configuration['formatter']['type'] = $form_state->getValue('formatter_type');
|
||||
// @todo Remove this manual cast after https://www.drupal.org/node/2635236
|
||||
// is resolved.
|
||||
$this->configuration['formatter']['settings'] = (array) $form_state->getValue('formatter_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the field definition.
|
||||
*
|
||||
* @return \Drupal\Core\Field\FieldDefinitionInterface
|
||||
*/
|
||||
protected function getFieldDefinition() {
|
||||
if (empty($this->fieldDefinition)) {
|
||||
$field_map = $this->entityFieldManager->getFieldMap();
|
||||
$bundle = reset($field_map[$this->entityTypeId][$this->fieldName]['bundles']);
|
||||
$field_definitions = $this->entityFieldManager->getFieldDefinitions($this->entityTypeId, $bundle);
|
||||
$this->fieldDefinition = $field_definitions[$this->fieldName];
|
||||
}
|
||||
return $this->fieldDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the field storage definition.
|
||||
*
|
||||
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface
|
||||
*/
|
||||
protected function getFieldStorageDefinition() {
|
||||
if (empty($this->fieldStorageDefinition)) {
|
||||
$field_definitions = $this->entityFieldManager->getFieldStorageDefinitions($this->entityTypeId);
|
||||
$this->fieldStorageDefinition = $field_definitions[$this->fieldName];
|
||||
}
|
||||
return $this->fieldStorageDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets field type definition.
|
||||
*
|
||||
* @return array
|
||||
* The field type definition.
|
||||
*/
|
||||
protected function getFieldTypeDefinition() {
|
||||
return $this->fieldTypeManager->getDefinition($this->getFieldStorageDefinition()->getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the formatter options for this field type.
|
||||
*
|
||||
* @return array
|
||||
* The formatter options.
|
||||
*/
|
||||
protected function getFormatterOptions() {
|
||||
return $this->formatterManager->getOptions($this->getFieldStorageDefinition()->getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the formatter object.
|
||||
*
|
||||
* @param string $type
|
||||
* The formatter name.
|
||||
* @param string $label
|
||||
* The label option for the formatter.
|
||||
* @param array $settings
|
||||
* The formatter settings.
|
||||
* @param array $third_party_settings
|
||||
* The formatter third party settings.
|
||||
*
|
||||
* @return \Drupal\Core\Field\FormatterInterface
|
||||
* The formatter object.
|
||||
*/
|
||||
protected function getFormatter($type, $label, array $settings, array $third_party_settings) {
|
||||
return $this->formatterManager->createInstance($type, [
|
||||
'field_definition' => $this->getFieldDefinition(),
|
||||
'view_mode' => 'default',
|
||||
'prepare' => TRUE,
|
||||
'label' => $label,
|
||||
'settings' => $settings,
|
||||
'third_party_settings' => $third_party_settings,
|
||||
]);
|
||||
}
|
||||
|
||||
public function __wakeup() {
|
||||
parent::__wakeup();
|
||||
// @todo figure out why this happens.
|
||||
// prevent $fieldStorageDefinition being erroneously set to $this.
|
||||
$this->fieldStorageDefinition = NULL;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ctools_block\Plugin\Deriver;
|
||||
|
||||
use Drupal\Core\Plugin\Context\ContextDefinition;
|
||||
use Drupal\ctools\Plugin\Deriver\EntityDeriverBase;
|
||||
|
||||
/**
|
||||
* Provides entity field block definitions for every field.
|
||||
*/
|
||||
class EntityFieldDeriver extends EntityDeriverBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
$entity_type_labels = $this->entityManager->getEntityTypeLabels();
|
||||
foreach ($this->entityManager->getFieldMap() as $entity_type_id => $entity_field_map) {
|
||||
foreach ($this->entityManager->getFieldStorageDefinitions($entity_type_id) as $field_storage_definition) {
|
||||
$field_name = $field_storage_definition->getName();
|
||||
|
||||
// The blocks are based on fields. However, we are looping through field
|
||||
// storages for which no fields may exist. If that is the case, skip
|
||||
// this field storage.
|
||||
if (!isset($entity_field_map[$field_name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$field_info = $entity_field_map[$field_name];
|
||||
$derivative_id = $entity_type_id . ":" . $field_name;
|
||||
|
||||
// Get the admin label for both base and configurable fields.
|
||||
if ($field_storage_definition->isBaseField()) {
|
||||
$admin_label = $field_storage_definition->getLabel();
|
||||
}
|
||||
else {
|
||||
// We take the field label used on the first bundle.
|
||||
$first_bundle = reset($field_info['bundles']);
|
||||
$bundle_field_definitions = $this->entityManager->getFieldDefinitions($entity_type_id, $first_bundle);
|
||||
|
||||
// The field storage config may exist, but it's possible that no
|
||||
// fields are actually using it. If that's the case, skip to the next
|
||||
// field.
|
||||
if (empty($bundle_field_definitions[$field_name])) {
|
||||
continue;
|
||||
}
|
||||
$admin_label = $bundle_field_definitions[$field_name]->getLabel();
|
||||
}
|
||||
|
||||
// Set plugin definition for derivative.
|
||||
$derivative = $base_plugin_definition;
|
||||
$derivative['category'] = $this->t('@entity', ['@entity' => $entity_type_labels[$entity_type_id]]);
|
||||
$derivative['admin_label'] = $admin_label;
|
||||
$derivative['context'] = [
|
||||
'entity' => new ContextDefinition('entity:' . $entity_type_id, $entity_type_labels[$entity_type_id], TRUE),
|
||||
];
|
||||
|
||||
$this->derivatives[$derivative_id] = $derivative;
|
||||
|
||||
}
|
||||
}
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- node.type.ctools_block_field_test
|
||||
_core:
|
||||
default_config_hash: hoRuk0InNhhRVGnhQ9hzifTXVz432i9hvPe-tVstUbc
|
||||
id: node.ctools_block_field_test.promote
|
||||
field_name: promote
|
||||
entity_type: node
|
||||
bundle: ctools_block_field_test
|
||||
label: 'Promoted to front page'
|
||||
description: ''
|
||||
required: false
|
||||
translatable: true
|
||||
default_value:
|
||||
-
|
||||
value: 0
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
on_label: 'On'
|
||||
off_label: 'Off'
|
||||
field_type: boolean
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.field.node.ctools_block_field_test.body
|
||||
- node.type.ctools_block_field_test
|
||||
module:
|
||||
- path
|
||||
- text
|
||||
_core:
|
||||
default_config_hash: xpDeWNLzjX4dDB9YyBlO9y9FR4vHwMv0GKsuqDYy0Bc
|
||||
id: node.ctools_block_field_test.default
|
||||
targetEntityType: node
|
||||
bundle: ctools_block_field_test
|
||||
mode: default
|
||||
content:
|
||||
body:
|
||||
type: text_textarea_with_summary
|
||||
weight: 31
|
||||
settings:
|
||||
rows: 9
|
||||
summary_rows: 3
|
||||
placeholder: ''
|
||||
third_party_settings: { }
|
||||
created:
|
||||
type: datetime_timestamp
|
||||
weight: 10
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
path:
|
||||
type: path
|
||||
weight: 30
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
promote:
|
||||
type: boolean_checkbox
|
||||
settings:
|
||||
display_label: true
|
||||
weight: 15
|
||||
third_party_settings: { }
|
||||
sticky:
|
||||
type: boolean_checkbox
|
||||
settings:
|
||||
display_label: true
|
||||
weight: 16
|
||||
third_party_settings: { }
|
||||
title:
|
||||
type: string_textfield
|
||||
weight: -5
|
||||
settings:
|
||||
size: 60
|
||||
placeholder: ''
|
||||
third_party_settings: { }
|
||||
uid:
|
||||
type: entity_reference_autocomplete
|
||||
weight: 5
|
||||
settings:
|
||||
match_operator: CONTAINS
|
||||
size: 60
|
||||
placeholder: ''
|
||||
third_party_settings: { }
|
||||
hidden: { }
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.field.node.ctools_block_field_test.body
|
||||
- node.type.ctools_block_field_test
|
||||
module:
|
||||
- user
|
||||
_core:
|
||||
default_config_hash: clNnyw6fhh5SwIme5I_3zjbLv-PMfpY-JXofVAC3CV8
|
||||
id: node.ctools_block_field_test.default
|
||||
targetEntityType: node
|
||||
bundle: ctools_block_field_test
|
||||
mode: default
|
||||
content: { }
|
||||
hidden:
|
||||
body: true
|
||||
links: true
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- core.entity_view_mode.node.teaser
|
||||
- field.field.node.ctools_block_field_test.body
|
||||
- node.type.ctools_block_field_test
|
||||
module:
|
||||
- text
|
||||
- user
|
||||
_core:
|
||||
default_config_hash: gQV5baI7pCIOBUtLkbJ1c2WJwM8CdlKiKOtLLIWnfy0
|
||||
id: node.ctools_block_field_test.teaser
|
||||
targetEntityType: node
|
||||
bundle: ctools_block_field_test
|
||||
mode: teaser
|
||||
content:
|
||||
body:
|
||||
label: hidden
|
||||
type: text_summary_or_trimmed
|
||||
weight: 101
|
||||
settings:
|
||||
trim_length: 600
|
||||
third_party_settings: { }
|
||||
links:
|
||||
weight: 100
|
||||
hidden: { }
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.node.body
|
||||
- node.type.ctools_block_field_test
|
||||
module:
|
||||
- text
|
||||
_core:
|
||||
default_config_hash: fzUnwtwftRsgKExjpF6XdMqbUzP16ytkjQniBZl1Hqg
|
||||
id: node.ctools_block_field_test.body
|
||||
field_name: body
|
||||
entity_type: node
|
||||
bundle: ctools_block_field_test
|
||||
label: Body
|
||||
description: ''
|
||||
required: false
|
||||
translatable: true
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
display_summary: true
|
||||
field_type: text_with_summary
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.node.field_image
|
||||
- node.type.ctools_block_field_test
|
||||
module:
|
||||
- image
|
||||
id: node.ctools_block_field_test.field_image
|
||||
field_name: field_image
|
||||
entity_type: node
|
||||
bundle: ctools_block_field_test
|
||||
label: Image
|
||||
description: ''
|
||||
required: false
|
||||
translatable: true
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
file_directory: '[date:custom:Y]-[date:custom:m]'
|
||||
file_extensions: 'png gif jpg jpeg'
|
||||
max_filesize: ''
|
||||
max_resolution: ''
|
||||
min_resolution: ''
|
||||
alt_field: true
|
||||
title_field: false
|
||||
alt_field_required: true
|
||||
title_field_required: false
|
||||
default_image:
|
||||
uuid: null
|
||||
alt: ''
|
||||
title: ''
|
||||
width: null
|
||||
height: null
|
||||
handler: 'default:file'
|
||||
handler_settings: { }
|
||||
field_type: image
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- file
|
||||
- image
|
||||
- node
|
||||
id: node.field_image
|
||||
field_name: field_image
|
||||
entity_type: node
|
||||
type: image
|
||||
settings:
|
||||
uri_scheme: public
|
||||
default_image:
|
||||
uuid: null
|
||||
alt: ''
|
||||
title: ''
|
||||
width: null
|
||||
height: null
|
||||
target_type: file
|
||||
display_field: false
|
||||
display_default: false
|
||||
module: image
|
||||
locked: false
|
||||
cardinality: 1
|
||||
translatable: true
|
||||
indexes:
|
||||
target_id:
|
||||
- target_id
|
||||
persist_with_no_fields: false
|
||||
custom_storage: false
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- menu_ui
|
||||
third_party_settings:
|
||||
menu_ui:
|
||||
available_menus: { }
|
||||
parent: ''
|
||||
_core:
|
||||
default_config_hash: hjC271ZF6B5XYgF6-F5Ak73sJiZWJRmSLsgk8S7Vo-8
|
||||
name: 'CTools block field test'
|
||||
type: ctools_block_field_test
|
||||
description: 'A content type used for the ctools_block field tests.'
|
||||
help: ''
|
||||
new_revision: false
|
||||
preview_mode: 0
|
||||
display_submitted: false
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
name: 'Chaos tools blocks test'
|
||||
type: module
|
||||
description: 'Support module for Chaos tools blocks tests.'
|
||||
# core: 8.x
|
||||
package: Testing
|
||||
# version: 8.0.1
|
||||
dependencies:
|
||||
- image
|
||||
- menu_ui
|
||||
- node
|
||||
- path
|
||||
- text
|
||||
- user
|
||||
features: true
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-04-28
|
||||
version: '8.x-3.0'
|
||||
core: '8.x'
|
||||
project: 'ctools'
|
||||
datestamp: 1493401747
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\ctools_block\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the entity field block.
|
||||
*
|
||||
* @group ctools_block
|
||||
*/
|
||||
class EntityFieldBlockTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['block', 'ctools_block', 'ctools_block_field_test'];
|
||||
|
||||
/**
|
||||
* Tests using the node body field in a block.
|
||||
*/
|
||||
public function testBodyField() {
|
||||
$block = $this->drupalPlaceBlock('entity_field:node:body', [
|
||||
'formatter' => [
|
||||
'type' => 'text_default',
|
||||
],
|
||||
'context_mapping' => [
|
||||
'entity' => '@node.node_route_context:node',
|
||||
],
|
||||
]);
|
||||
$node = $this->drupalCreateNode(['type' => 'ctools_block_field_test']);
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
$assert = $this->assertSession();
|
||||
$assert->pageTextContains($block->label());
|
||||
$assert->pageTextContains($node->body->value);
|
||||
|
||||
$node->set('body', NULL)->save();
|
||||
$this->getSession()->reload();
|
||||
// The block should not appear if there is no value in the field.
|
||||
$assert->pageTextNotContains($block->label());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that empty image fields will still render their default value.
|
||||
*/
|
||||
public function testEmptyImageField() {
|
||||
$source = \Drupal::moduleHandler()->getModule('image')->getPath() . '/sample.png';
|
||||
file_unmanaged_copy($source, 'public://sample.png');
|
||||
|
||||
/** @var \Drupal\file\FileInterface $file */
|
||||
$file = \Drupal::entityTypeManager()
|
||||
->getStorage('file')
|
||||
->create([
|
||||
'uri' => 'public://sample.png',
|
||||
]);
|
||||
$file->save();
|
||||
|
||||
/** @var \Drupal\field\FieldConfigInterface $field */
|
||||
$field = \Drupal::entityTypeManager()
|
||||
->getStorage('field_config')
|
||||
->load('node.ctools_block_field_test.field_image');
|
||||
$settings = $field->getSettings();
|
||||
$settings['default_image']['uuid'] = $file->uuid();
|
||||
$field->set('settings', $settings)->save();
|
||||
|
||||
$this->drupalPlaceBlock('entity_field:node:field_image', [
|
||||
'formatter' => [
|
||||
'type' => 'image_image',
|
||||
],
|
||||
'context_mapping' => [
|
||||
'entity' => '@node.node_route_context:node',
|
||||
],
|
||||
]);
|
||||
|
||||
$node = $this->drupalCreateNode(['type' => 'ctools_block_field_test']);
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
|
||||
$url = $file->getFileUri();
|
||||
$url = file_create_url($url);
|
||||
$url = file_url_transform_relative($url);
|
||||
$this->assertSession()->responseContains('src="' . $url . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests using the node uid base field in a block.
|
||||
*/
|
||||
public function testNodeBaseFields() {
|
||||
$block = $this->drupalPlaceBlock('entity_field:node:title', [
|
||||
'formatter' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
'context_mapping' => [
|
||||
'entity' => '@node.node_route_context:node',
|
||||
],
|
||||
]);
|
||||
$node = $this->drupalCreateNode(['type' => 'ctools_block_field_test', 'uid' => 1]);
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
$assert = $this->assertSession();
|
||||
$assert->pageTextContains($block->label());
|
||||
$assert->pageTextContains($node->getTitle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we are setting the render cache metadata correctly.
|
||||
*/
|
||||
public function testRenderCache() {
|
||||
$this->drupalPlaceBlock('entity_field:node:body', [
|
||||
'formatter' => [
|
||||
'type' => 'text_default',
|
||||
],
|
||||
'context_mapping' => [
|
||||
'entity' => '@node.node_route_context:node',
|
||||
],
|
||||
]);
|
||||
$a = $this->drupalCreateNode(['type' => 'ctools_block_field_test']);
|
||||
$b = $this->drupalCreateNode(['type' => 'ctools_block_field_test']);
|
||||
|
||||
$assert = $this->assertSession();
|
||||
$this->drupalGet('node/' . $a->id());
|
||||
$assert->pageTextContains($a->body->value);
|
||||
$this->drupalGet('node/' . $b->id());
|
||||
$assert->pageTextNotContains($a->body->value);
|
||||
$assert->pageTextContains($b->body->value);
|
||||
|
||||
$text = 'This is my text. Are you not entertained?';
|
||||
$a->body->value = $text;
|
||||
$a->save();
|
||||
$this->drupalGet('node/' . $a->id());
|
||||
$assert->pageTextContains($text);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
name: Chaos tools Views
|
||||
type: module
|
||||
description: 'A set of improvements to the core Views code that allows for greater control over Blocks.'
|
||||
package: Chaos tool suite (Experimental)
|
||||
# version: 3.x
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- block
|
||||
- views
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-04-28
|
||||
version: '8.x-3.0'
|
||||
core: '8.x'
|
||||
project: 'ctools'
|
||||
datestamp: 1493401747
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
use Drupal\views\Plugin\views\display\Block as CoreBlock;
|
||||
use Drupal\ctools_views\Plugin\Display\Block;
|
||||
|
||||
/**
|
||||
* Implements hook_views_plugins_display_alter().
|
||||
*/
|
||||
function ctools_views_views_plugins_display_alter(&$displays) {
|
||||
if (!empty($displays['block']['class']) && $displays['block']['class'] == CoreBlock::class) {
|
||||
$displays['block']['class'] = Block::class;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_config_schema_info_alter().
|
||||
*/
|
||||
function ctools_views_config_schema_info_alter(&$definitions) {
|
||||
// Add to the views block plugin schema.
|
||||
$definitions['views_block']['mapping']['pager'] = [
|
||||
'type' => 'string',
|
||||
'label' => 'Pager type'
|
||||
];
|
||||
$definitions['views_block']['mapping']['fields'] = [
|
||||
'type' => 'sequence',
|
||||
'label' => 'Fields settings',
|
||||
'sequence' => [
|
||||
[
|
||||
'type' => 'mapping',
|
||||
'label' => 'Field settings',
|
||||
'mapping' => [
|
||||
'hide' => [
|
||||
'type' => 'boolean',
|
||||
'label' => 'Hide field',
|
||||
],
|
||||
'weight' => [
|
||||
'type' => 'integer',
|
||||
'label' => 'Field weight',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$definitions['views_block']['mapping']['filter'] = [
|
||||
'type' => 'sequence',
|
||||
'label' => 'Filters settings',
|
||||
'sequence' => [
|
||||
[
|
||||
'type' => 'mapping',
|
||||
'label' => 'Filter settings',
|
||||
'mapping' => [
|
||||
'type' => [
|
||||
'type' => 'string',
|
||||
'label' => 'Plugin id',
|
||||
],
|
||||
'disable' => [
|
||||
'type' => 'boolean',
|
||||
'label' => 'Disable filter',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$definitions['views_block']['mapping']['sort'] = [
|
||||
'type' => 'sequence',
|
||||
'label' => 'Sort settings',
|
||||
'sequence' => [
|
||||
[
|
||||
'type' => 'string',
|
||||
'label' => 'Sort order value',
|
||||
],
|
||||
],
|
||||
];
|
||||
$definitions['views_block']['mapping']['pager_offset'] = [
|
||||
'type' => 'integer',
|
||||
'label' => 'Pager offset'
|
||||
];
|
||||
|
||||
// Add to the views block display plugin schema.
|
||||
$definitions['views.display.block']['mapping']['allow']['mapping']['offset'] = [
|
||||
'type' => 'string',
|
||||
'label' => 'Pager offset',
|
||||
];
|
||||
$definitions['views.display.block']['mapping']['allow']['mapping']['pager'] = [
|
||||
'type' => 'string',
|
||||
'label' => 'Pager type',
|
||||
];
|
||||
$definitions['views.display.block']['mapping']['allow']['mapping']['hide_fields'] = [
|
||||
'type' => 'string',
|
||||
'label' => 'Hide fields',
|
||||
];
|
||||
$definitions['views.display.block']['mapping']['allow']['mapping']['sort_fields'] = [
|
||||
'type' => 'string',
|
||||
'label' => 'Sort fields',
|
||||
];
|
||||
$definitions['views.display.block']['mapping']['allow']['mapping']['disable_filters'] = [
|
||||
'type' => 'string',
|
||||
'label' => 'Disable filters',
|
||||
];
|
||||
$definitions['views.display.block']['mapping']['allow']['mapping']['configure_sorts'] = [
|
||||
'type' => 'string',
|
||||
'label' => 'Configure sorts',
|
||||
];
|
||||
}
|
||||
+438
@@ -0,0 +1,438 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ctools_views\Plugin\Display;
|
||||
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\views\Plugin\Block\ViewsBlock;
|
||||
use Drupal\views\Plugin\views\display\Block as CoreBlock;
|
||||
use Drupal\views\Plugin\views\filter\InOperator;
|
||||
|
||||
/**
|
||||
* Provides a Block display plugin that allows for greater control over Views
|
||||
* block settings.
|
||||
*/
|
||||
class Block extends CoreBlock {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function optionsSummary(&$categories, &$options) {
|
||||
parent::optionsSummary($categories, $options);
|
||||
$filtered_allow = array_filter($this->getOption('allow'));
|
||||
$filter_options = [
|
||||
'items_per_page' => $this->t('Items per page'),
|
||||
'offset' => $this->t('Pager offset'),
|
||||
'pager' => $this->t('Pager type'),
|
||||
'hide_fields' => $this->t('Hide fields'),
|
||||
'sort_fields' => $this->t('Reorder fields'),
|
||||
'disable_filters' => $this->t('Disable filters'),
|
||||
'configure_sorts' => $this->t('Configure sorts')
|
||||
];
|
||||
$filter_intersect = array_intersect_key($filter_options, $filtered_allow);
|
||||
|
||||
$options['allow'] = array(
|
||||
'category' => 'block',
|
||||
'title' => $this->t('Allow settings'),
|
||||
'value' => empty($filtered_allow) ? $this->t('None') : implode(', ', $filter_intersect),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
$options = $form['allow']['#options'];
|
||||
$options['offset'] = $this->t('Pager offset');
|
||||
$options['pager'] = $this->t('Pager type');
|
||||
$options['hide_fields'] = $this->t('Hide fields');
|
||||
$options['sort_fields'] = $this->t('Reorder fields');
|
||||
$options['disable_filters'] = $this->t('Disable filters');
|
||||
$options['configure_sorts'] = $this->t('Configure sorts');
|
||||
$form['allow']['#options'] = $options;
|
||||
// Update the items_per_page if set.
|
||||
$defaults = array_filter($form['allow']['#default_value']);
|
||||
if (isset($defaults['items_per_page'])) {
|
||||
$defaults['items_per_page'] = 'items_per_page';
|
||||
}
|
||||
$form['allow']['#default_value'] = $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockForm(ViewsBlock $block, array &$form, FormStateInterface $form_state) {
|
||||
$form = parent::blockForm($block, $form, $form_state);
|
||||
|
||||
$allow_settings = array_filter($this->getOption('allow'));
|
||||
$block_configuration = $block->getConfiguration();
|
||||
|
||||
// Modify "Items per page" block settings form.
|
||||
if (!empty($allow_settings['items_per_page'])) {
|
||||
// Items per page
|
||||
$form['override']['items_per_page']['#type'] = 'number';
|
||||
unset($form['override']['items_per_page']['#options']);
|
||||
}
|
||||
|
||||
// Provide "Pager offset" block settings form.
|
||||
if (!empty($allow_settings['offset'])) {
|
||||
$form['override']['pager_offset'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Pager offset'),
|
||||
'#default_value' => isset($block_configuration['pager_offset']) ? $block_configuration['pager_offset'] : 0,
|
||||
'#description' => $this->t('For example, set this to 3 and the first 3 items will not be displayed.'),
|
||||
];
|
||||
}
|
||||
|
||||
// Provide "Pager type" block settings form.
|
||||
if (!empty($allow_settings['pager'])) {
|
||||
$pager_options = [
|
||||
'view' => $this->t('Inherit from view'),
|
||||
'some' => $this->t('Display a specified number of items'),
|
||||
'none' => $this->t('Display all items')
|
||||
];
|
||||
$form['override']['pager'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Pager'),
|
||||
'#options' => $pager_options,
|
||||
'#default_value' => isset($block_configuration['pager']) ? $block_configuration['pager'] : 'view'
|
||||
];
|
||||
}
|
||||
|
||||
// Provide "Hide fields" / "Reorder fields" block settings form.
|
||||
if (!empty($allow_settings['hide_fields']) || !empty($allow_settings['sort_fields'])) {
|
||||
// Set up the configuration table for hiding / sorting fields.
|
||||
$fields = $this->getHandlers('field');
|
||||
$header = [];
|
||||
if (!empty($allow_settings['hide_fields'])) {
|
||||
$header['hide'] = $this->t('Hide');
|
||||
}
|
||||
$header['label'] = $this->t('Label');
|
||||
if (!empty($allow_settings['sort_fields'])) {
|
||||
$header['weight'] = $this->t('Weight');
|
||||
}
|
||||
$form['override']['order_fields'] = [
|
||||
'#type' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => array(),
|
||||
];
|
||||
if (!empty($allow_settings['sort_fields'])) {
|
||||
$form['override']['order_fields']['#tabledrag'] = [
|
||||
[
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => 'field-weight',
|
||||
]
|
||||
];
|
||||
$form['override']['order_fields']['#attributes'] = ['id' => 'order-fields'];
|
||||
}
|
||||
|
||||
// Sort available field plugins by their currently configured weight.
|
||||
$sorted_fields = [];
|
||||
if (!empty($allow_settings['sort_fields']) && isset($block_configuration['fields'])) {
|
||||
uasort($block_configuration['fields'], '\Drupal\ctools_views\Plugin\Display\Block::sortFieldsByWeight');
|
||||
foreach (array_keys($block_configuration['fields']) as $field_name) {
|
||||
if (!empty($fields[$field_name])) {
|
||||
$sorted_fields[$field_name] = $fields[$field_name];
|
||||
unset($fields[$field_name]);
|
||||
}
|
||||
}
|
||||
if (!empty($fields)) {
|
||||
foreach ($fields as $field_name => $field_info) {
|
||||
$sorted_fields[$field_name] = $field_info;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$sorted_fields = $fields;
|
||||
}
|
||||
|
||||
// Add each field to the configuration table.
|
||||
foreach ($sorted_fields as $field_name => $plugin) {
|
||||
$field_label = $plugin->adminLabel();
|
||||
if (!empty($plugin->options['label'])) {
|
||||
$field_label .= ' (' . $plugin->options['label'] . ')';
|
||||
}
|
||||
if (!empty($allow_settings['sort_fields'])) {
|
||||
$form['override']['order_fields'][$field_name]['#attributes']['class'][] = 'draggable';
|
||||
}
|
||||
$form['override']['order_fields'][$field_name]['#weight'] = !empty($block_configuration['fields'][$field_name]['weight']) ? $block_configuration['fields'][$field_name]['weight'] : '';
|
||||
if (!empty($allow_settings['hide_fields'])) {
|
||||
$form['override']['order_fields'][$field_name]['hide'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => !empty($block_configuration['fields'][$field_name]['hide']) ? $block_configuration['fields'][$field_name]['hide'] : 0,
|
||||
];
|
||||
}
|
||||
$form['override']['order_fields'][$field_name]['label'] = [
|
||||
'#markup' => $field_label,
|
||||
];
|
||||
if (!empty($allow_settings['sort_fields'])) {
|
||||
$form['override']['order_fields'][$field_name]['weight'] = [
|
||||
'#type' => 'weight',
|
||||
'#title' => $this->t('Weight for @title', ['@title' => $field_label]),
|
||||
'#title_display' => 'invisible',
|
||||
'#delta' => 50,
|
||||
'#default_value' => !empty($block_configuration['fields'][$field_name]['weight']) ? $block_configuration['fields'][$field_name]['weight'] : 0,
|
||||
'#attributes' => ['class' => ['field-weight']],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Provide "Configure filters" / "Disable filters" block settings form.
|
||||
if (!empty($allow_settings['disable_filters'])) {
|
||||
$items = [];
|
||||
foreach ((array) $this->getOption('filters') as $filter_name => $item) {
|
||||
$item['value'] = isset($block_configuration["filter"][$filter_name]['value']) ? $block_configuration["filter"][$filter_name]['value'] : '';
|
||||
$items[$filter_name] = $item;
|
||||
}
|
||||
$this->setOption('filters', $items);
|
||||
$filters = $this->getHandlers('filter');
|
||||
|
||||
// Add a settings form for each exposed filter to configure or hide it.
|
||||
foreach ($filters as $filter_name => $plugin) {
|
||||
if ($plugin->isExposed() && $exposed_info = $plugin->exposedInfo()) {
|
||||
$form['override']['filters'][$filter_name] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $exposed_info['label'],
|
||||
];
|
||||
$form['override']['filters'][$filter_name]['plugin'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $plugin,
|
||||
];
|
||||
// Render "Disable filters" settings form.
|
||||
if (!empty($allow_settings['disable_filters'])) {
|
||||
$form['override']['filters'][$filter_name]['disable'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Disable'),
|
||||
'#default_value' => !empty($block_configuration['filter'][$filter_name]['disable']) ? $block_configuration['filter'][$filter_name]['disable'] : 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Provide "Configure sorts" block settings form.
|
||||
if (!empty($allow_settings['configure_sorts'])) {
|
||||
$sorts = $this->getHandlers('sort');
|
||||
$options = array(
|
||||
'ASC' => $this->t('Sort ascending'),
|
||||
'DESC' => $this->t('Sort descending'),
|
||||
);
|
||||
foreach ($sorts as $sort_name => $plugin) {
|
||||
$form['override']['sort'][$sort_name] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $plugin->adminLabel(),
|
||||
];
|
||||
$form['override']['sort'][$sort_name]['plugin'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $plugin,
|
||||
];
|
||||
$form['override']['sort'][$sort_name]['order'] = array(
|
||||
'#title' => $this->t('Order'),
|
||||
'#type' => 'radios',
|
||||
'#options' => $options,
|
||||
'#default_value' => $plugin->options['order']
|
||||
);
|
||||
|
||||
// Set default values for sorts for this block.
|
||||
if (!empty($block_configuration["sort"][$sort_name])) {
|
||||
$form['override']['sort'][$sort_name]['order']['#default_value'] = $block_configuration["sort"][$sort_name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockSubmit(ViewsBlock $block, $form, FormStateInterface $form_state) {
|
||||
// Set default value for items_per_page if left blank.
|
||||
if (empty($form_state->getValue(array('override', 'items_per_page')))) {
|
||||
$form_state->setValue(array('override', 'items_per_page'), "none");
|
||||
}
|
||||
|
||||
parent::blockSubmit($block, $form, $form_state);
|
||||
$configuration = $block->getConfiguration();
|
||||
$allow_settings = array_filter($this->getOption('allow'));
|
||||
|
||||
// Save "Pager type" settings to block configuration.
|
||||
if (!empty($allow_settings['pager'])) {
|
||||
if ($pager = $form_state->getValue(['override', 'pager'])) {
|
||||
$configuration['pager'] = $pager;
|
||||
}
|
||||
}
|
||||
|
||||
// Save "Pager offset" settings to block configuration.
|
||||
if (!empty($allow_settings['offset'])) {
|
||||
$configuration['pager_offset'] = $form_state->getValue(['override', 'pager_offset']);
|
||||
}
|
||||
|
||||
// Save "Hide fields" / "Reorder fields" settings to block configuration.
|
||||
if (!empty($allow_settings['hide_fields']) || !empty($allow_settings['sort_fields'])) {
|
||||
if ($fields = array_filter($form_state->getValue(['override', 'order_fields']))) {
|
||||
uasort($fields, '\Drupal\ctools_views\Plugin\Display\Block::sortFieldsByWeight');
|
||||
$configuration['fields'] = $fields;
|
||||
}
|
||||
}
|
||||
|
||||
// Save "Configure filters" / "Disable filters" settings to block
|
||||
// configuration.
|
||||
unset($configuration['filter']);
|
||||
if (!empty($allow_settings['disable_filters'])) {
|
||||
if ($filters = $form_state->getValue(['override', 'filters'])) {
|
||||
foreach ($filters as $filter_name => $filter) {
|
||||
/** @var \Drupal\views\Plugin\views\filter\FilterPluginBase $plugin */
|
||||
$plugin = $form_state->getValue(['override', 'filters', $filter_name, 'plugin']);
|
||||
$configuration["filter"][$filter_name]['type'] = $plugin->getPluginId();
|
||||
|
||||
// Check if we want to disable this filter.
|
||||
if (!empty($allow_settings['disable_filters'])) {
|
||||
$disable = $form_state->getValue(['override', 'filters', $filter_name, 'disable']);
|
||||
// If marked disabled, we don't really care about other stuff.
|
||||
if ($disable) {
|
||||
$configuration["filter"][$filter_name]['disable'] = $disable;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save "Configure sorts" settings to block configuration.
|
||||
if (!empty($allow_settings['configure_sorts'])) {
|
||||
$sorts = $form_state->getValue(['override', 'sort']);
|
||||
foreach ($sorts as $sort_name => $sort) {
|
||||
$plugin = $sort['plugin'];
|
||||
// Check if we want to override the default sort order
|
||||
if ($plugin->options['order'] != $sort['order']) {
|
||||
$configuration['sort'][$sort_name] = $sort['order'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$block->setConfiguration($configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preBlockBuild(ViewsBlock $block) {
|
||||
parent::preBlockBuild($block);
|
||||
|
||||
$allow_settings = array_filter($this->getOption('allow'));
|
||||
$config = $block->getConfiguration();
|
||||
list(, $display_id) = explode('-', $block->getDerivativeId(), 2);
|
||||
|
||||
// Change pager offset settings based on block configuration.
|
||||
if (!empty($allow_settings['offset'])) {
|
||||
$this->view->setOffset($config['pager_offset']);
|
||||
}
|
||||
|
||||
// Change pager style settings based on block configuration.
|
||||
if (!empty($allow_settings['pager'])) {
|
||||
$pager = $this->view->display_handler->getOption('pager');
|
||||
if (!empty($config['pager']) && $config['pager'] != 'view') {
|
||||
$pager['type'] = $config['pager'];
|
||||
}
|
||||
$this->view->display_handler->setOption('pager', $pager);
|
||||
}
|
||||
|
||||
// Change fields output based on block configuration.
|
||||
if (!empty($allow_settings['hide_fields']) || !empty($allow_settings['sort_fields'])) {
|
||||
if (!empty($config['fields']) && $this->view->getStyle()->usesFields()) {
|
||||
$fields = $this->view->getHandlers('field');
|
||||
uasort($config['fields'], '\Drupal\ctools_views\Plugin\Display\Block::sortFieldsByWeight');
|
||||
$iterate_fields = !empty($allow_settings['sort_fields']) ? $config['fields'] : $fields;
|
||||
foreach (array_keys($iterate_fields) as $field_name) {
|
||||
// Remove each field in sequence and re-add them to sort
|
||||
// appropriately or hide if disabled.
|
||||
$this->view->removeHandler($display_id, 'field', $field_name);
|
||||
if (empty($allow_settings['hide_fields']) || (!empty($allow_settings['hide_fields']) && empty($config['fields'][$field_name]['hide']))) {
|
||||
$this->view->addHandler($display_id, 'field', $fields[$field_name]['table'], $fields[$field_name]['field'], $fields[$field_name], $field_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Change filters output based on block configuration.
|
||||
if (!empty($allow_settings['disable_filters'])) {
|
||||
$filters = $this->view->getHandlers('filter', $display_id);
|
||||
foreach ($filters as $filter_name => $filter) {
|
||||
// If we allow disabled filters and this filter is disabled, disable it
|
||||
// and continue.
|
||||
if (!empty($allow_settings['disable_filters']) && !empty($config["filter"][$filter_name]['disable'])) {
|
||||
$this->view->removeHandler($display_id, 'filter', $filter_name);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Change sorts based on block configuration.
|
||||
if (!empty($allow_settings['configure_sorts'])) {
|
||||
$sorts = $this->view->getHandlers('sort', $display_id);
|
||||
foreach ($sorts as $sort_name => $sort) {
|
||||
if (!empty($config["sort"][$sort_name])) {
|
||||
$sort['order'] = $config["sort"][$sort_name];
|
||||
$this->view->setHandler($display_id, 'sort', $sort_name, $sort);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getFilterOptionsValue(array $filter, array $config) {
|
||||
$plugin_definition = \Drupal::service('plugin.manager.views.filter')->getDefinition($config['type']);
|
||||
if (is_subclass_of($plugin_definition['class'], '\Drupal\views\Plugin\views\filter\InOperator')) {
|
||||
return array_values($config['value']);
|
||||
}
|
||||
return $config['value'][$filter['expose']['identifier']];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function usesExposed() {
|
||||
$filters = $this->getHandlers('filter');
|
||||
foreach ($filters as $filter_name => $filter) {
|
||||
if ($filter->isExposed() && !empty($filter->exposedInfo())) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposed widgets typically only work with ajax in Drupal core, however
|
||||
* #2605218 totally breaks the rest of the functionality in this display and
|
||||
* in Core's Block display as well, so we allow non-ajax block views to use
|
||||
* exposed filters and manually set the #action to the current request uri.
|
||||
*/
|
||||
public function elementPreRender(array $element) {
|
||||
/** @var \Drupal\views\ViewExecutable $view */
|
||||
$view = $element['#view'];
|
||||
if (!empty($view->exposed_widgets['#action']) && !$view->ajaxEnabled()) {
|
||||
$view->exposed_widgets['#action'] = \Drupal::request()->getRequestUri();
|
||||
}
|
||||
return parent::elementPreRender($element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort field config array by weight.
|
||||
*
|
||||
* @param $a
|
||||
* @param $b
|
||||
* @return int
|
||||
*/
|
||||
public static function sortFieldsByWeight($a, $b) {
|
||||
$a_weight = isset($a['weight']) ? $a['weight'] : 0;
|
||||
$b_weight = isset($b['weight']) ? $b['weight'] : 0;
|
||||
if ($a_weight == $b_weight) {
|
||||
return 0;
|
||||
}
|
||||
return ($a_weight < $b_weight) ? -1 : 1;
|
||||
}
|
||||
|
||||
}
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ctools_views\Tests;
|
||||
|
||||
use Drupal\views_ui\Tests\UITestBase;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Tests the ctools_views block display plugin
|
||||
* overriding settings from a basic View.
|
||||
*
|
||||
* @group ctools_views
|
||||
* @see \Drupal\ctools_views\Plugin\Display\Block
|
||||
*/
|
||||
class CToolsViewsBasicViewBlockTest extends UITestBase {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('ctools_views', 'ctools_views_test_views');
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = array('ctools_views_test_view');
|
||||
|
||||
/**
|
||||
* The block storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
ViewTestData::createTestViews(get_class($this), array('ctools_views_test_views'));
|
||||
$this->storage = $this->container->get('entity.manager')->getStorage('block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ctools_views "items_per_page" configuration.
|
||||
*/
|
||||
public function testItemsPerPage() {
|
||||
$default_theme = $this->config('system.theme')->get('default');
|
||||
|
||||
// Get the "Configure block" form for our Views block.
|
||||
$this->drupalGet('admin/structure/block/add/views_block:ctools_views_test_view-block_pager/' . $default_theme);
|
||||
$this->assertFieldByXPath('//input[@type="number" and @name="settings[override][items_per_page]"]', NULL, 'items_per_page setting is a number field');
|
||||
// Add block to sidebar_first region with default settings.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$edit['settings[override][items_per_page]'] = 0;
|
||||
$this->drupalPostForm('admin/structure/block/add/views_block:ctools_views_test_view-block_pager/' . $default_theme, $edit, $this->t('Save block'));
|
||||
|
||||
// Assert items per page default settings.
|
||||
$this->drupalGet('<front>');
|
||||
$result = $this->xpath('//div[contains(@class, "region-sidebar-first")]/div[contains(@class, "block-views")]/h2');
|
||||
$this->assertEqual((string) $result[0], 'CTools Views Pager Block');
|
||||
$this->assertRaw('Showing 3 records on page 1');
|
||||
$this->assertEqual(3, count($this->xpath('//div[contains(@class, "view-display-id-block_pager")]//table/tbody/tr')));
|
||||
|
||||
// Override items per page settings.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$edit['settings[override][items_per_page]'] = 2;
|
||||
$this->drupalPostForm('admin/structure/block/manage/views_block__ctools_views_test_view_block_pager', $edit, $this->t('Save block'));
|
||||
|
||||
$block = $this->storage->load('views_block__ctools_views_test_view_block_pager');
|
||||
$config = $block->getPlugin()->getConfiguration();
|
||||
$this->assertEqual(2, $config['items_per_page'], "'Items per page' is properly saved.");
|
||||
|
||||
// Assert items per page overridden settings.
|
||||
$this->drupalGet('<front>');
|
||||
$result = $this->xpath('//div[contains(@class, "region-sidebar-first")]/div[contains(@class, "block-views")]/h2');
|
||||
$this->assertEqual((string) $result[0], 'CTools Views Pager Block');
|
||||
$this->assertRaw('Showing 2 records on page 1');
|
||||
$this->assertEqual(2, count($this->xpath('//div[contains(@class, "view-display-id-block_pager")]//table/tbody/tr')));
|
||||
$this->assertEqual([1, 2], $this->xpath('//div[contains(@class, "view-display-id-block_pager")]//table//tr//td[contains(@class, "views-field-id")]'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ctools_views "offset" configuration.
|
||||
*/
|
||||
public function testOffset() {
|
||||
$default_theme = $this->config('system.theme')->get('default');
|
||||
|
||||
// Get the "Configure block" form for our Views block.
|
||||
$this->drupalGet('admin/structure/block/add/views_block:ctools_views_test_view-block_pager/' . $default_theme);
|
||||
$this->assertFieldByXPath('//input[@type="number" and @name="settings[override][pager_offset]"]', NULL, 'items_per_page setting is a number field');
|
||||
// Add block to sidebar_first region with default settings.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$edit['settings[override][items_per_page]'] = 0;
|
||||
$this->drupalPostForm('admin/structure/block/add/views_block:ctools_views_test_view-block_pager/' . $default_theme, $edit, $this->t('Save block'));
|
||||
|
||||
// Assert pager offset default settings.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertEqual([1, 2, 3], $this->xpath('//div[contains(@class, "view-display-id-block_pager")]//table//tr//td[contains(@class, "views-field-id")]'));
|
||||
|
||||
// Override pager offset settings.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$edit['settings[override][items_per_page]'] = 0;
|
||||
$edit['settings[override][pager_offset]'] = 1;
|
||||
$this->drupalPostForm('admin/structure/block/manage/views_block__ctools_views_test_view_block_pager', $edit, $this->t('Save block'));
|
||||
|
||||
$block = $this->storage->load('views_block__ctools_views_test_view_block_pager');
|
||||
$config = $block->getPlugin()->getConfiguration();
|
||||
$this->assertEqual(1, $config['pager_offset'], "'Pager offset' is properly saved.");
|
||||
|
||||
// Assert pager offset overridden settings.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertEqual([2, 3, 4], $this->xpath('//div[contains(@class, "view-display-id-block_pager")]//table//tr//td[contains(@class, "views-field-id")]'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ctools_views "pager" configuration.
|
||||
*/
|
||||
public function testPager() {
|
||||
$default_theme = $this->config('system.theme')->get('default');
|
||||
|
||||
// Get the "Configure block" form for our Views block.
|
||||
$this->drupalGet('admin/structure/block/add/views_block:ctools_views_test_view-block_pager/' . $default_theme);
|
||||
$this->assertFieldById('edit-settings-override-pager-view', 'view');
|
||||
$this->assertFieldById('edit-settings-override-pager-some');
|
||||
$this->assertFieldById('edit-settings-override-pager-none');
|
||||
|
||||
// Add block to sidebar_first region with default settings.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$edit['settings[override][items_per_page]'] = 0;
|
||||
$this->drupalPostForm('admin/structure/block/add/views_block:ctools_views_test_view-block_pager/' . $default_theme, $edit, $this->t('Save block'));
|
||||
|
||||
// Assert pager default settings.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertText('Page 1');
|
||||
$this->assertText('Next ›');
|
||||
|
||||
// Override pager settings to 'some'.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$edit['settings[override][items_per_page]'] = 0;
|
||||
$edit['settings[override][pager]'] = 'some';
|
||||
$this->drupalPostForm('admin/structure/block/manage/views_block__ctools_views_test_view_block_pager', $edit, $this->t('Save block'));
|
||||
|
||||
$block = $this->storage->load('views_block__ctools_views_test_view_block_pager');
|
||||
$config = $block->getPlugin()->getConfiguration();
|
||||
$this->assertEqual('some', $config['pager'], "'Pager' setting is properly saved.");
|
||||
|
||||
// Assert pager overridden settings to 'some', showing no pager.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertEqual(3, count($this->xpath('//div[contains(@class, "view-display-id-block_pager")]//table/tbody/tr')));
|
||||
$this->assertNoText('Page 1');
|
||||
$this->assertNoText('Next ›');
|
||||
|
||||
// Override pager settings to 'none'.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$edit['settings[override][items_per_page]'] = 0;
|
||||
$edit['settings[override][pager]'] = 'none';
|
||||
$this->drupalPostForm('admin/structure/block/manage/views_block__ctools_views_test_view_block_pager', $edit, $this->t('Save block'));
|
||||
|
||||
$block = $this->storage->load('views_block__ctools_views_test_view_block_pager');
|
||||
$config = $block->getPlugin()->getConfiguration();
|
||||
$this->assertEqual('none', $config['pager'], "'Pager' setting is properly saved.");
|
||||
|
||||
// Assert pager overridden settings to 'some', showing no pager.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertEqual(5, count($this->xpath('//div[contains(@class, "view-display-id-block_pager")]//table/tbody/tr')));
|
||||
$this->assertNoText('Page 1');
|
||||
$this->assertNoText('Next ›');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ctools_views 'hide_fields' configuration.
|
||||
*/
|
||||
public function testHideFields() {
|
||||
$default_theme = $this->config('system.theme')->get('default');
|
||||
|
||||
// Get the "Configure block" form for our Views block.
|
||||
$this->drupalGet('admin/structure/block/add/views_block:ctools_views_test_view-block_fields/' . $default_theme);
|
||||
$this->assertFieldById('edit-settings-override-order-fields-id-hide');
|
||||
|
||||
// Add block to sidebar_first region with default settings.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$this->drupalPostForm('admin/structure/block/add/views_block:ctools_views_test_view-block_fields/' . $default_theme, $edit, $this->t('Save block'));
|
||||
|
||||
// Assert hide_fields default settings.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertEqual(5, count($this->xpath('//div[contains(@class, "view-display-id-block_fields")]//table//td[contains(@class, "views-field-id")]')));
|
||||
|
||||
// Override hide_fields settings.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$edit['settings[override][order_fields][id][hide]'] = 1;
|
||||
$this->drupalPostForm('admin/structure/block/manage/views_block__ctools_views_test_view_block_fields', $edit, $this->t('Save block'));
|
||||
|
||||
$block = $this->storage->load('views_block__ctools_views_test_view_block_fields');
|
||||
$config = $block->getPlugin()->getConfiguration();
|
||||
$this->assertEqual(1, $config['fields']['id']['hide'], "'hide_fields' setting is properly saved.");
|
||||
$this->assertEqual(0, $config['fields']['name']['hide'], "'hide_fields' setting is properly saved.");
|
||||
|
||||
// Assert hide_fields overridden settings.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertEqual(0, count($this->xpath('//div[contains(@class, "view-display-id-block_fields")]//table//td[contains(@class, "views-field-id")]')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ctools_views 'sort_fields' configuration.
|
||||
*/
|
||||
public function testOrderFields() {
|
||||
$default_theme = $this->config('system.theme')->get('default');
|
||||
|
||||
// Get the "Configure block" form for our Views block.
|
||||
$this->drupalGet('admin/structure/block/add/views_block:ctools_views_test_view-block_fields/' . $default_theme);
|
||||
$this->assertFieldById('edit-settings-override-order-fields-id-weight', 0);
|
||||
|
||||
// Add block to sidebar_first region with default settings.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$this->drupalPostForm('admin/structure/block/add/views_block:ctools_views_test_view-block_fields/' . $default_theme, $edit, $this->t('Save block'));
|
||||
|
||||
// Assert sort_fields default settings.
|
||||
$this->drupalGet('<front>');
|
||||
// Check that the td with class "views-field-id" is the first td in the first tr element.
|
||||
$this->assertEqual(0, count($this->xpath('count(//div[contains(@class, "view-display-id-block_fields")]//table//tr[1]//td[contains(@class, "views-field-id")]/preceding-sibling::td)')));
|
||||
|
||||
// Override sort_fields settings.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$edit['settings[override][order_fields][name][weight]'] = -50;
|
||||
$edit['settings[override][order_fields][age][weight]'] = -49;
|
||||
$edit['settings[override][order_fields][job][weight]'] = -48;
|
||||
$edit['settings[override][order_fields][created][weight]'] = -47;
|
||||
$edit['settings[override][order_fields][id][weight]'] = -46;
|
||||
$edit['settings[override][order_fields][name_1][weight]'] = -45;
|
||||
$this->drupalPostForm('admin/structure/block/manage/views_block__ctools_views_test_view_block_fields', $edit, $this->t('Save block'));
|
||||
|
||||
$block = $this->storage->load('views_block__ctools_views_test_view_block_fields');
|
||||
$config = $block->getPlugin()->getConfiguration();
|
||||
$this->assertEqual(-46, $config['fields']['id']['weight'], "'sort_fields' setting is properly saved.");
|
||||
$this->assertEqual(-50, $config['fields']['name']['weight'], "'sort_fields' setting is properly saved.");
|
||||
|
||||
// Assert sort_fields overridden settings.
|
||||
$this->drupalGet('<front>');
|
||||
|
||||
// Check that the td with class "views-field-id" is the 5th td in the first tr element.
|
||||
$this->assertEqual(4, count($this->xpath('//div[contains(@class, "view-display-id-block_fields")]//table//tr[1]//td[contains(@class, "views-field-id")]/preceding-sibling::td')));
|
||||
|
||||
// Check that duplicate fields in the View produce expected outpu
|
||||
$name1_element = $this->xpath('//div[contains(@class, "view-display-id-block_fields")]//table//tr[1]/td[contains(@class, "views-field-name")]/text()');
|
||||
$name1 = (string) $name1_element[0];
|
||||
$this->assertEqual("John", trim($name1));
|
||||
$name2_element = $this->xpath('//div[contains(@class, "view-display-id-block_fields")]//table//tr[1]/td[contains(@class, "views-field-name-1")]/text()');
|
||||
$name2 = (string) $name2_element[0];
|
||||
$this->assertEqual("John", trim($name2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ctools_views 'disable_filters' configuration.
|
||||
*/
|
||||
public function testDisableFilters() {
|
||||
$default_theme = $this->config('system.theme')->get('default');
|
||||
|
||||
// Get the "Configure block" form for our Views block.
|
||||
$this->drupalGet('admin/structure/block/add/views_block:ctools_views_test_view-block_filter/' . $default_theme);
|
||||
$this->assertFieldById('edit-settings-override-filters-status-disable');
|
||||
$this->assertFieldById('edit-settings-override-filters-job-disable');
|
||||
|
||||
// Add block to sidebar_first region with default settings.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$this->drupalPostForm('admin/structure/block/add/views_block:ctools_views_test_view-block_filter/' . $default_theme, $edit, $this->t('Save block'));
|
||||
|
||||
// Assert disable_filters default settings.
|
||||
$this->drupalGet('<front>');
|
||||
// Check that the default settings show both filters
|
||||
$this->assertFieldByXPath('//select[@name="status"]');
|
||||
$this->assertFieldByXPath('//input[@name="job"]');
|
||||
|
||||
// Override disable_filters settings.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$edit['settings[override][filters][status][disable]'] = 1;
|
||||
$edit['settings[override][filters][job][disable]'] = 1;
|
||||
$this->drupalPostForm('admin/structure/block/manage/views_block__ctools_views_test_view_block_filter', $edit, $this->t('Save block'));
|
||||
|
||||
$block = $this->storage->load('views_block__ctools_views_test_view_block_filter');
|
||||
$config = $block->getPlugin()->getConfiguration();
|
||||
$this->assertEqual(1, $config['filter']['status']['disable'], "'disable_filters' setting is properly saved.");
|
||||
$this->assertEqual(1, $config['filter']['job']['disable'], "'disable_filters' setting is properly saved.");
|
||||
|
||||
// Assert disable_filters overridden settings.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertNoFieldByXPath('//select[@name="status"]');
|
||||
$this->assertNoFieldByXPath('//input[@name="job"]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ctools_views 'configure_sorts' configuration.
|
||||
*/
|
||||
public function testConfigureSorts() {
|
||||
$default_theme = $this->config('system.theme')->get('default');
|
||||
|
||||
// Get the "Configure block" form for our Views block.
|
||||
$this->drupalGet('admin/structure/block/add/views_block:ctools_views_test_view-block_sort/' . $default_theme);
|
||||
$this->assertFieldByXPath('//input[@name="settings[override][sort][id][order]"]');
|
||||
|
||||
// Add block to sidebar_first region with default settings.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$this->drupalPostForm('admin/structure/block/add/views_block:ctools_views_test_view-block_sort/' . $default_theme, $edit, $this->t('Save block'));
|
||||
|
||||
// Assert configure_sorts default settings.
|
||||
$this->drupalGet('<front>');
|
||||
// Check that the results are sorted ASC
|
||||
$element = $this->xpath('//div[contains(@class, "view-display-id-block_sort")]//table//tr[1]/td[1]/text()');
|
||||
$value = (string) $element[0];
|
||||
$this->assertEqual("1", trim($value));
|
||||
|
||||
// Override configure_sorts settings.
|
||||
$edit = array();
|
||||
$edit['region'] = 'sidebar_first';
|
||||
$edit['settings[override][sort][id][order]'] = "DESC";
|
||||
$this->drupalPostForm('admin/structure/block/manage/views_block__ctools_views_test_view_block_sort', $edit, $this->t('Save block'));
|
||||
|
||||
$block = $this->storage->load('views_block__ctools_views_test_view_block_sort');
|
||||
$config = $block->getPlugin()->getConfiguration();
|
||||
$this->assertEqual("DESC", $config['sort']['id'], "'configure_sorts' setting is properly saved.");
|
||||
|
||||
// Assert configure_sorts overridden settings.
|
||||
// Check that the results are sorted DESC
|
||||
$this->drupalGet('<front>');
|
||||
$element = $this->xpath('//div[contains(@class, "view-display-id-block_sort")]//table//tr[1]/td[1]/text()');
|
||||
$value = (string) $element[0];
|
||||
$this->assertEqual("5", trim($value));
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
name: 'CTools Views test views'
|
||||
type: module
|
||||
description: 'Provides default views for CTools Views tests.'
|
||||
package: Testing
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- views
|
||||
- block
|
||||
- entity_test
|
||||
- ctools_views
|
||||
- text
|
||||
- user
|
||||
- node
|
||||
- taxonomy
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-04-28
|
||||
version: '8.x-3.0'
|
||||
core: '8.x'
|
||||
project: 'ctools'
|
||||
datestamp: 1493401747
|
||||
+543
@@ -0,0 +1,543 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- node.type.ctools_views
|
||||
- taxonomy.vocabulary.tags
|
||||
module:
|
||||
- datetime
|
||||
- node
|
||||
- options
|
||||
- taxonomy
|
||||
- user
|
||||
id: ctools_views_entity_test
|
||||
label: 'CTools Views Entity Test View'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
offset: 0
|
||||
style:
|
||||
type: table
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
settings:
|
||||
link_to_entity: true
|
||||
plugin_id: field
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Title
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
filters:
|
||||
status:
|
||||
value: true
|
||||
table: node_field_data
|
||||
field: status
|
||||
plugin_id: boolean
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
value:
|
||||
ctools_views: ctools_views
|
||||
entity_type: node
|
||||
entity_field: type
|
||||
plugin_id: bundle
|
||||
sorts:
|
||||
created:
|
||||
id: created
|
||||
table: node_field_data
|
||||
field: created
|
||||
order: DESC
|
||||
entity_type: node
|
||||
entity_field: created
|
||||
plugin_id: date
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
granularity: second
|
||||
title: 'CTools Views Entity Test View'
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
block_filter_date:
|
||||
display_plugin: block
|
||||
id: block_filter_date
|
||||
display_title: 'Date filter'
|
||||
position: 4
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
display_description: ''
|
||||
title: 'Date filter'
|
||||
defaults:
|
||||
title: false
|
||||
filters: false
|
||||
filter_groups: false
|
||||
filters:
|
||||
status:
|
||||
value: true
|
||||
table: node_field_data
|
||||
field: status
|
||||
plugin_id: boolean
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
value:
|
||||
ctools_views: ctools_views
|
||||
entity_type: node
|
||||
entity_field: type
|
||||
plugin_id: bundle
|
||||
group: 1
|
||||
field_ctools_views_date_value:
|
||||
id: field_ctools_views_date_value
|
||||
table: node__field_ctools_views_date
|
||||
field: field_ctools_views_date_value
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: between
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: field_ctools_views_date_value_op
|
||||
label: 'CTools Views Date (field_ctools_views_date)'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: field_ctools_views_date_value_op
|
||||
identifier: field_ctools_views_date_value
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator1: '0'
|
||||
administrator: '0'
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
plugin_id: datetime
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups:
|
||||
1: AND
|
||||
allow:
|
||||
items_per_page: false
|
||||
offset: '0'
|
||||
pager: '0'
|
||||
hide_fields: '0'
|
||||
sort_fields: '0'
|
||||
disable_filters: '0'
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
block_filter_list:
|
||||
display_plugin: block
|
||||
id: block_filter_list
|
||||
display_title: 'List filter'
|
||||
position: 3
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
display_description: ''
|
||||
title: 'List filter'
|
||||
defaults:
|
||||
title: false
|
||||
filters: false
|
||||
filter_groups: false
|
||||
filters:
|
||||
status:
|
||||
value: true
|
||||
table: node_field_data
|
||||
field: status
|
||||
plugin_id: boolean
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
value:
|
||||
ctools_views: ctools_views
|
||||
entity_type: node
|
||||
entity_field: type
|
||||
plugin_id: bundle
|
||||
field_ctools_views_list_value:
|
||||
id: field_ctools_views_list_value
|
||||
table: node__field_ctools_views_list
|
||||
field: field_ctools_views_list_value
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: or
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: field_ctools_views_list_value_op
|
||||
label: 'Ctools Views List (field_ctools_views_list)'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: field_ctools_views_list_value_op
|
||||
identifier: field_ctools_views_list_value
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator1: '0'
|
||||
administrator: '0'
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
reduce_duplicates: false
|
||||
plugin_id: list_field
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups:
|
||||
1: AND
|
||||
allow:
|
||||
items_per_page: false
|
||||
offset: '0'
|
||||
pager: '0'
|
||||
hide_fields: '0'
|
||||
sort_fields: '0'
|
||||
disable_filters: '0'
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
block_filter_tax:
|
||||
display_plugin: block
|
||||
id: block_filter_tax
|
||||
display_title: 'Taxonomy filter'
|
||||
position: 2
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
display_description: ''
|
||||
title: 'Taxonomy filter'
|
||||
defaults:
|
||||
title: false
|
||||
filters: false
|
||||
filter_groups: false
|
||||
filters:
|
||||
status:
|
||||
value: true
|
||||
table: node_field_data
|
||||
field: status
|
||||
plugin_id: boolean
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
value:
|
||||
ctools_views: ctools_views
|
||||
entity_type: node
|
||||
entity_field: type
|
||||
plugin_id: bundle
|
||||
group: 1
|
||||
field_ctools_views_tags_target_id:
|
||||
id: field_ctools_views_tags_target_id
|
||||
table: node__field_ctools_views_tags
|
||||
field: field_ctools_views_tags_target_id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: or
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: field_ctools_views_tags_target_id_op
|
||||
label: 'Tags (field_ctools_views_tags)'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: field_ctools_views_tags_target_id_op
|
||||
identifier: field_ctools_views_tags_target_id
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator1: '0'
|
||||
administrator: '0'
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
reduce_duplicates: false
|
||||
type: select
|
||||
limit: true
|
||||
vid: tags
|
||||
hierarchy: false
|
||||
error_message: true
|
||||
plugin_id: taxonomy_index_tid
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups:
|
||||
1: AND
|
||||
allow:
|
||||
items_per_page: false
|
||||
offset: '0'
|
||||
pager: '0'
|
||||
hide_fields: '0'
|
||||
sort_fields: '0'
|
||||
disable_filters: '0'
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- user
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
block_filter_text:
|
||||
display_plugin: block
|
||||
id: block_filter_text
|
||||
display_title: 'Textfield filter'
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
display_description: ''
|
||||
title: 'Textfield filter'
|
||||
defaults:
|
||||
title: false
|
||||
filters: false
|
||||
filter_groups: false
|
||||
filters:
|
||||
status:
|
||||
value: true
|
||||
table: node_field_data
|
||||
field: status
|
||||
plugin_id: boolean
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
type:
|
||||
id: type
|
||||
table: node_field_data
|
||||
field: type
|
||||
value:
|
||||
ctools_views: ctools_views
|
||||
entity_type: node
|
||||
entity_field: type
|
||||
plugin_id: bundle
|
||||
field_ctools_views_text_value:
|
||||
id: field_ctools_views_text_value
|
||||
table: node__field_ctools_views_text
|
||||
field: field_ctools_views_text_value
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: '='
|
||||
value: ''
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: field_ctools_views_text_value_op
|
||||
label: 'Text (field_ctools_views_text)'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: field_ctools_views_text_value_op
|
||||
identifier: field_ctools_views_text_value
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator1: '0'
|
||||
administrator: '0'
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
plugin_id: string
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups:
|
||||
1: AND
|
||||
allow:
|
||||
items_per_page: false
|
||||
offset: '0'
|
||||
pager: '0'
|
||||
hide_fields: '0'
|
||||
sort_fields: '0'
|
||||
disable_filters: '0'
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
+949
@@ -0,0 +1,949 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
id: ctools_views_test_view
|
||||
label: 'CTools Views Test View'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: views_test_data
|
||||
base_field: id
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
options: { }
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
offset: 0
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
override: true
|
||||
sticky: false
|
||||
caption: ''
|
||||
summary: ''
|
||||
description: ''
|
||||
columns:
|
||||
id: id
|
||||
age: age
|
||||
created: created
|
||||
id_1: id_1
|
||||
job: job
|
||||
name: name
|
||||
info:
|
||||
id:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
age:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
created:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
id_1:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
job:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
name:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
default: '-1'
|
||||
empty_table: false
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
id:
|
||||
id: id
|
||||
table: views_test_data
|
||||
field: id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ID
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
set_precision: false
|
||||
precision: 0
|
||||
decimal: .
|
||||
separator: ''
|
||||
format_plural: false
|
||||
format_plural_string: "1\x03@count"
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
entity_type: null
|
||||
entity_field: null
|
||||
plugin_id: numeric
|
||||
name:
|
||||
id: name
|
||||
table: views_test_data
|
||||
field: name
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Name
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: standard
|
||||
age:
|
||||
id: age
|
||||
table: views_test_data
|
||||
field: age
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Age
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
set_precision: false
|
||||
precision: 0
|
||||
decimal: .
|
||||
separator: ''
|
||||
format_plural: false
|
||||
format_plural_string: "1\x03@count"
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
plugin_id: numeric
|
||||
job:
|
||||
id: job
|
||||
table: views_test_data
|
||||
field: job
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Job
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: standard
|
||||
created:
|
||||
id: created
|
||||
table: views_test_data
|
||||
field: created
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Created
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
date_format: fallback
|
||||
custom_date_format: ''
|
||||
timezone: ''
|
||||
plugin_id: date
|
||||
filters: { }
|
||||
sorts:
|
||||
id:
|
||||
id: id
|
||||
table: views_test_data
|
||||
field: id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
plugin_id: standard
|
||||
title: 'CTools Views Test View'
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
use_ajax: false
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups: { }
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
tags: { }
|
||||
block_fields:
|
||||
display_plugin: block
|
||||
id: block_fields
|
||||
display_title: 'CTools Views Fields Block'
|
||||
position: 2
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
block_category: 'CTools Views'
|
||||
allow:
|
||||
hide_fields: hide_fields
|
||||
sort_fields: sort_fields
|
||||
items_per_page: false
|
||||
offset: '0'
|
||||
pager: '0'
|
||||
disable_filters: '0'
|
||||
block_description: 'CTools Views Fields Block'
|
||||
display_description: ''
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
offset: 0
|
||||
defaults:
|
||||
pager: false
|
||||
title: false
|
||||
fields: false
|
||||
title: 'CTools Views Fields Block'
|
||||
fields:
|
||||
id:
|
||||
id: id
|
||||
table: views_test_data
|
||||
field: id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ID
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
set_precision: false
|
||||
precision: 0
|
||||
decimal: .
|
||||
separator: ''
|
||||
format_plural: false
|
||||
format_plural_string: "1\x03@count"
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
entity_type: null
|
||||
entity_field: null
|
||||
plugin_id: numeric
|
||||
name:
|
||||
id: name
|
||||
table: views_test_data
|
||||
field: name
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Name
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: standard
|
||||
age:
|
||||
id: age
|
||||
table: views_test_data
|
||||
field: age
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Age
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
set_precision: false
|
||||
precision: 0
|
||||
decimal: .
|
||||
separator: ''
|
||||
format_plural: false
|
||||
format_plural_string: "1\x03@count"
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
plugin_id: numeric
|
||||
job:
|
||||
id: job
|
||||
table: views_test_data
|
||||
field: job
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Job
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: standard
|
||||
created:
|
||||
id: created
|
||||
table: views_test_data
|
||||
field: created
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Created
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
date_format: fallback
|
||||
custom_date_format: ''
|
||||
timezone: ''
|
||||
plugin_id: date
|
||||
name_1:
|
||||
id: name_1
|
||||
table: views_test_data
|
||||
field: name
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: '2nd name field'
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: standard
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
tags: { }
|
||||
block_filter:
|
||||
display_plugin: block
|
||||
id: block_filter
|
||||
display_title: 'CTools Views Filter Block'
|
||||
position: 3
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
display_description: ''
|
||||
block_category: 'CTools Views'
|
||||
block_description: 'CTools Views Filter Block'
|
||||
allow:
|
||||
disable_filters: disable_filters
|
||||
items_per_page: false
|
||||
offset: '0'
|
||||
pager: '0'
|
||||
hide_fields: '0'
|
||||
sort_fields: '0'
|
||||
filters:
|
||||
status:
|
||||
id: status
|
||||
table: views_test_data
|
||||
field: status
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: '='
|
||||
value: true
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: Status
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: status_op
|
||||
identifier: status
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator1: '0'
|
||||
administrator: '0'
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
plugin_id: boolean
|
||||
job:
|
||||
id: job
|
||||
table: views_test_data
|
||||
field: job
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: '='
|
||||
value: ''
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: job_op
|
||||
label: Job
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: job_op
|
||||
identifier: job
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator1: '0'
|
||||
administrator: '0'
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
plugin_id: string
|
||||
defaults:
|
||||
filters: false
|
||||
filter_groups: false
|
||||
title: false
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups:
|
||||
1: AND
|
||||
title: 'CTools Views Filter Block'
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
tags: { }
|
||||
block_pager:
|
||||
display_plugin: block
|
||||
id: block_pager
|
||||
display_title: 'CTools Views Pager Block'
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
block_description: 'CTools Views Pager Block'
|
||||
block_category: 'CTools Views'
|
||||
allow:
|
||||
items_per_page: true
|
||||
offset: offset
|
||||
pager: pager
|
||||
hide_fields: '0'
|
||||
sort_fields: '0'
|
||||
disable_filters: '0'
|
||||
display_description: ''
|
||||
header:
|
||||
result:
|
||||
id: result
|
||||
table: views
|
||||
field: result
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
empty: false
|
||||
content: "Displaying @start - @end of @total\nShowing @current_record_count records on page @current_page"
|
||||
plugin_id: result
|
||||
defaults:
|
||||
header: false
|
||||
pager: false
|
||||
title: false
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
items_per_page: 3
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
tags:
|
||||
previous: '‹ Previous'
|
||||
next: 'Next ›'
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
title: 'CTools Views Pager Block'
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
tags: { }
|
||||
block_sort:
|
||||
display_plugin: block
|
||||
id: block_sort
|
||||
display_title: 'CTools Views Sort Block'
|
||||
position: 4
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
display_description: ''
|
||||
title: 'CTools Views Sort Block'
|
||||
defaults:
|
||||
title: false
|
||||
block_description: 'CTools Views Sort Block'
|
||||
block_category: 'CTools Views'
|
||||
allow:
|
||||
configure_sorts: configure_sorts
|
||||
items_per_page: false
|
||||
offset: '0'
|
||||
pager: '0'
|
||||
hide_fields: '0'
|
||||
sort_fields: '0'
|
||||
disable_filters: '0'
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
tags: { }
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ctools\Access;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
interface AccessInterface {
|
||||
public function access(AccountInterface $account);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ctools\Access;
|
||||
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Routing\Access\AccessInterface as CoreAccessInterface;
|
||||
use Drupal\Core\Routing\RouteMatch;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\ctools\Access\AccessInterface as CToolsAccessInterface;
|
||||
use Drupal\user\SharedTempStoreFactory;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
class TempstoreAccess implements CoreAccessInterface {
|
||||
|
||||
/**
|
||||
* The shared tempstore factory.
|
||||
*
|
||||
* @var \Drupal\user\SharedTempStoreFactory
|
||||
*/
|
||||
protected $tempstore;
|
||||
|
||||
public function __construct(SharedTempStoreFactory $tempstore) {
|
||||
$this->tempstore = $tempstore;
|
||||
}
|
||||
|
||||
protected function getTempstore() {
|
||||
return $this->tempstore;
|
||||
}
|
||||
|
||||
public function access(Route $route, RouteMatch $match, AccountInterface $account) {
|
||||
$tempstore_id = $match->getParameter('tempstore_id') ? $match->getParameter('tempstore_id') : $route->getDefault('tempstore_id');
|
||||
$id = $match->getParameter($route->getRequirement('_ctools_access'));
|
||||
if ($tempstore_id && $id) {
|
||||
$cached_values = $this->getTempstore()->get($tempstore_id)->get($id);
|
||||
if (!empty($cached_values['access']) && ($cached_values['access'] instanceof CToolsAccessInterface)) {
|
||||
$access = $cached_values['access']->access($account);
|
||||
}
|
||||
else {
|
||||
$access = AccessResult::allowed();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$access = AccessResult::forbidden();
|
||||
}
|
||||
// The different wizards will have different tempstore ids and adding this
|
||||
// cache context allows us to nuance the access per wizard.
|
||||
$access->addCacheContexts(['url.query_args:tempstore_id']);
|
||||
return $access;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\ctools\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\OpenModalDialogCommand;
|
||||
|
||||
class OpenModalWizardCommand extends OpenModalDialogCommand {
|
||||
|
||||
public function __construct($object, $tempstore_id, array $parameters = array(), array $dialog_options = array(), $settings = NULL) {
|
||||
// Instantiate the wizard class properly.
|
||||
$parameters += [
|
||||
'tempstore_id' => $tempstore_id,
|
||||
'machine_name' => NULL,
|
||||
'step' => NULL,
|
||||
];
|
||||
$form = \Drupal::service('ctools.wizard.factory')->getWizardForm($object, $parameters, TRUE);
|
||||
$title = isset($form['#title']) ? $form['#title'] : '';
|
||||
$content = $form;
|
||||
|
||||
parent::__construct($title, $content, $dialog_options, $settings);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user