Compare commits
58
Commits
beta
..
alpha-0.6.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67ce55c047 | ||
|
|
ab9c1dcd4d | ||
|
|
f7225cdd01 | ||
|
|
65d95036c0 | ||
|
|
1904ab1352 | ||
|
|
77065246fc | ||
|
|
b6168089a7 | ||
|
|
249bc608b3 | ||
|
|
9c61825a17 | ||
|
|
f7c8728f20 | ||
|
|
7356c6cec6 | ||
|
|
7d11684731 | ||
|
|
86573ebbe9 | ||
|
|
e31f0c34a7 | ||
|
|
452f41108b | ||
|
|
d62d02201b | ||
|
|
cfe15cebd8 | ||
|
|
8133aa66f0 | ||
|
|
94175c1df1 | ||
|
|
82d5409957 | ||
|
|
07728aa425 | ||
|
|
29ad08648f | ||
|
|
a3a4e3922c | ||
|
|
76ff40f8bb | ||
|
|
c0fbcb7706 | ||
|
|
7e073fdfa0 | ||
|
|
1d0a95f022 | ||
|
|
9570ee1823 | ||
|
|
c5ae3d6b00 | ||
|
|
4967111a0e | ||
|
|
e27d1f1e5a | ||
|
|
df54d07169 | ||
|
|
efca592b17 | ||
|
|
95cd2c15cf | ||
|
|
00166e7917 | ||
|
|
ea435b7517 | ||
|
|
6d88ca7c8d | ||
|
|
d4087b2924 | ||
|
|
a01d52e62c | ||
|
|
252149bfdc | ||
|
|
cb2ae7ee35 | ||
|
|
10f68b951d | ||
|
|
0d555a596e | ||
|
|
d485286289 | ||
|
|
97dd8cdf97 | ||
|
|
97ec3584d8 | ||
|
|
e9a00903a9 | ||
|
|
9c6fe22012 | ||
|
|
741849397e | ||
|
|
679e1f09ad | ||
|
|
162ab2b949 | ||
|
|
c0b6dc6b75 | ||
|
|
848f03f4a4 | ||
|
|
ac821854ce | ||
|
|
1ae73c1c12 | ||
|
|
07a138cfe6 | ||
|
|
1d3a3dd6fd | ||
|
|
b33159e3d0 |
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"globals": {
|
||||
"_paq": true
|
||||
}
|
||||
}
|
||||
@@ -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,109 @@
|
||||
|
||||
Module: Piwik - Web analytics
|
||||
Author: Alexander Hass <http://drupal.org/user/85918>
|
||||
|
||||
|
||||
Description
|
||||
===========
|
||||
Adds the Piwik tracking system to your website.
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
* Piwik installation
|
||||
* Piwik website ID
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
* Copy the 'piwik' module directory in to your Drupal 'modules'
|
||||
directory as usual.
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
In the settings page enter your Piwik website ID.
|
||||
|
||||
All pages will now have the required JavaScript added to the
|
||||
HTML footer can confirm this by viewing the page source from
|
||||
your browser.
|
||||
|
||||
Page specific tracking
|
||||
====================================================
|
||||
The default is set to "Add to every page except the listed pages". By
|
||||
default the following pages are listed for exclusion:
|
||||
|
||||
/admin
|
||||
/admin/*
|
||||
/batch
|
||||
/node/add*
|
||||
/node/*/*
|
||||
/user/*/*
|
||||
|
||||
These defaults are changeable by the website administrator or any other
|
||||
user with 'Administer Piwik' permission.
|
||||
|
||||
Like the blocks visibility settings in Drupal core, there is a choice for
|
||||
"Add if the following PHP code returns TRUE." Sample PHP snippets that can be
|
||||
used in this textarea can be found on the handbook page "Overview-approach to
|
||||
block visibility" at http://drupal.org/node/64135.
|
||||
|
||||
Custom variables
|
||||
=================
|
||||
One example for custom variables tracking is the "User roles" tracking. Enter
|
||||
the below configuration data into the custom variables settings form under
|
||||
admin/config/system/piwik.
|
||||
|
||||
Slot: 1
|
||||
Name: User roles
|
||||
Value: [current-user:piwik-role-names]
|
||||
Scope: Visitor
|
||||
|
||||
Slot: 1
|
||||
Name: User ids
|
||||
Value: [current-user:piwik-role-ids]
|
||||
Scope: Visitor
|
||||
|
||||
More details about custom variables can be found in the Piwik API documentation
|
||||
at http://piwik.org/docs/javascript-tracking/#toc-custom-variables.
|
||||
|
||||
|
||||
Advanced Settings
|
||||
=================
|
||||
You can include additional JavaScript snippets in the custom javascript
|
||||
code textarea. These can be found on various blog posts, or on the
|
||||
official Piwik pages. Support is not provided for any customisations
|
||||
you include.
|
||||
|
||||
To speed up page loading you may also cache the Piwik "piwik.js"
|
||||
file locally.
|
||||
|
||||
Known issues
|
||||
============
|
||||
Drupal requirements (http://drupal.org/requirements) tell you to configure
|
||||
PHP with "session.save_handler = user", but your Piwik installation may
|
||||
not work with this configuration and gives you a server error 500.
|
||||
|
||||
1. You are able to workaround with the PHP default in your php.ini:
|
||||
|
||||
[Session]
|
||||
session.save_handler = files
|
||||
|
||||
2. With Apache you may overwrite the PHP setting for the Piwik directory only.
|
||||
If Piwik is installed in /piwik you are able to create a .htaccess file in
|
||||
this directory with the below code:
|
||||
|
||||
# PHP 4, Apache 1.
|
||||
<IfModule mod_php4.c>
|
||||
php_value session.save_handler files
|
||||
</IfModule>
|
||||
|
||||
# PHP 4, Apache 2.
|
||||
<IfModule sapi_apache2.c>
|
||||
php_value session.save_handler files
|
||||
</IfModule>
|
||||
|
||||
# PHP 5, Apache 1 and 2.
|
||||
<IfModule mod_php5.c>
|
||||
php_value session.save_handler files
|
||||
</IfModule>
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "drupal/piwik",
|
||||
"description": "Adds Piwik javascript tracking code to all your site's pages",
|
||||
"type": "drupal-module",
|
||||
"homepage": "https://www.drupal.org/project/piwik",
|
||||
"authors": [
|
||||
{
|
||||
"name": "hass",
|
||||
"homepage": "https://www.drupal.org/u/hass"
|
||||
},
|
||||
{
|
||||
"name": "See other contributors",
|
||||
"homepage":"https://www.drupal.org/node/247808/committers"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://www.drupal.org/project/issues/piwik",
|
||||
"source": "http://git.drupal.org/project/piwik.git"
|
||||
},
|
||||
"license": "GPL-2.0+",
|
||||
"require": {}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
site_id: ''
|
||||
url_http: ''
|
||||
url_https: ''
|
||||
domain_mode: 0
|
||||
visibility:
|
||||
request_path_mode: 0
|
||||
request_path_pages: "/admin\n/admin/*\n/batch\n/node/add*\n/node/*/*\n/user/*/*"
|
||||
user_role_mode: 0
|
||||
user_role_roles: { }
|
||||
user_account_mode: 0
|
||||
track:
|
||||
mailto: true
|
||||
files: true
|
||||
files_extensions: '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc(x|m)?|dot(x|m)?|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt(x|m)?|pot(x|m)?|pps(x|m)?|ppam|sld(x|m)?|thmx|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls(x|m|b)?|xlt(x|m)|xlam|xml|z|zip'
|
||||
colorbox: true
|
||||
userid: false
|
||||
messages: { }
|
||||
site_search: false
|
||||
privacy:
|
||||
donottrack: true
|
||||
custom:
|
||||
variable: { }
|
||||
codesnippet:
|
||||
before: ''
|
||||
after: ''
|
||||
translation_set: false
|
||||
cache: false
|
||||
page_title_hierarchy: false
|
||||
page_title_hierarchy_exclude_home: true
|
||||
@@ -0,0 +1,120 @@
|
||||
# Schema for the configuration files of the piwik module.
|
||||
|
||||
piwik.settings:
|
||||
type: config_object
|
||||
label: 'Piwik settings'
|
||||
mapping:
|
||||
site_id:
|
||||
type: string
|
||||
label: 'Piwik site ID'
|
||||
url_http:
|
||||
type: string
|
||||
label: 'Piwik HTTP URL'
|
||||
url_https:
|
||||
type: string
|
||||
label: 'Piwik HTTPS URL'
|
||||
domain_mode:
|
||||
type: integer
|
||||
label: 'What are you tracking?'
|
||||
visibility:
|
||||
type: mapping
|
||||
label: 'Visibility'
|
||||
mapping:
|
||||
request_path_mode:
|
||||
type: integer
|
||||
label: 'Add tracking to specific pages'
|
||||
request_path_pages:
|
||||
type: string
|
||||
label: 'Pages by their paths'
|
||||
user_role_mode:
|
||||
type: integer
|
||||
label: 'Add tracking for specific roles'
|
||||
user_role_roles:
|
||||
type: sequence
|
||||
label: 'Roles'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Role'
|
||||
user_account_mode:
|
||||
type: integer
|
||||
label: 'Allow users to customize tracking on their account page'
|
||||
track:
|
||||
type: mapping
|
||||
label: 'Links and downloads'
|
||||
mapping:
|
||||
mailto:
|
||||
type: boolean
|
||||
label: 'Track clicks on mailto links'
|
||||
files:
|
||||
type: boolean
|
||||
label: 'Track downloads'
|
||||
files_extensions:
|
||||
type: string
|
||||
label: 'File extension list'
|
||||
colorbox:
|
||||
type: boolean
|
||||
label: 'Track content in colorbox modal dialogs'
|
||||
userid:
|
||||
type: boolean
|
||||
label: 'Track User ID'
|
||||
messages:
|
||||
type: sequence
|
||||
label: 'Track messages of type'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Message type'
|
||||
site_search:
|
||||
type: boolean
|
||||
label: 'Track internal search'
|
||||
privacy:
|
||||
type: mapping
|
||||
label: 'Privacy'
|
||||
mapping:
|
||||
donottrack:
|
||||
type: boolean
|
||||
label: 'Universal web tracking opt-out'
|
||||
custom:
|
||||
type: mapping
|
||||
label: 'Custom variables'
|
||||
mapping:
|
||||
variable:
|
||||
type: sequence
|
||||
label: 'Custom variable'
|
||||
sequence:
|
||||
type: mapping
|
||||
label: 'Variable'
|
||||
mapping:
|
||||
slot:
|
||||
type: integer
|
||||
label: 'Slot number'
|
||||
name:
|
||||
type: string
|
||||
label: 'The custom variable name.'
|
||||
value:
|
||||
type: string
|
||||
label: 'The custom variable value.'
|
||||
scope:
|
||||
type: string
|
||||
label: 'The scope for the custom variable.'
|
||||
codesnippet:
|
||||
type: mapping
|
||||
label: 'Custom code snippets'
|
||||
mapping:
|
||||
before:
|
||||
type: string
|
||||
label: 'Code snippet (before)'
|
||||
after:
|
||||
type: string
|
||||
label: 'Code snippet (after)'
|
||||
translation_set:
|
||||
type: boolean
|
||||
label: 'Track translation sets as one unit'
|
||||
cache:
|
||||
type: boolean
|
||||
label: 'Locally cache tracking code file'
|
||||
page_title_hierarchy:
|
||||
type: boolean
|
||||
label: 'Show page titles as hierarchy like breadcrumbs'
|
||||
page_title_hierarchy_exclude_home:
|
||||
type: boolean
|
||||
label: 'Hide home page from hierarchy'
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* @file
|
||||
* Piwik admin behaviors.
|
||||
*/
|
||||
|
||||
(function ($, window) {
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Provide the summary information for the tracking settings vertical tabs.
|
||||
*/
|
||||
Drupal.behaviors.trackingSettingsSummary = {
|
||||
attach: function () {
|
||||
// Make sure this behavior is processed only if drupalSetSummary is defined.
|
||||
if (typeof jQuery.fn.drupalSetSummary === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#edit-page-visibility-settings').drupalSetSummary(function (context) {
|
||||
var $radio = $('input[name="piwik_visibility_request_path_mode"]:checked', context);
|
||||
if ($radio.val() === '0') {
|
||||
if (!$('textarea[name="piwik_visibility_request_path_pages"]', context).val()) {
|
||||
return Drupal.t('Not restricted');
|
||||
}
|
||||
else {
|
||||
return Drupal.t('All pages with exceptions');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return Drupal.t('Restricted to certain pages');
|
||||
}
|
||||
});
|
||||
|
||||
$('#edit-role-visibility-settings').drupalSetSummary(function (context) {
|
||||
var vals = [];
|
||||
$('input[type="checkbox"]:checked', context).each(function () {
|
||||
vals.push($.trim($(this).next('label').text()));
|
||||
});
|
||||
if (!vals.length) {
|
||||
return Drupal.t('Not restricted');
|
||||
}
|
||||
else if ($('input[name="piwik_visibility_user_role_mode"]:checked', context).val() === '1') {
|
||||
return Drupal.t('Excepted: @roles', {'@roles': vals.join(', ')});
|
||||
}
|
||||
else {
|
||||
return vals.join(', ');
|
||||
}
|
||||
});
|
||||
|
||||
$('#edit-user-visibility-settings').drupalSetSummary(function (context) {
|
||||
var $radio = $('input[name="piwik_visibility_user_account_mode"]:checked', context);
|
||||
if ($radio.val() === '0') {
|
||||
return Drupal.t('Not customizable');
|
||||
}
|
||||
else if ($radio.val() === '1') {
|
||||
return Drupal.t('On by default with opt out');
|
||||
}
|
||||
else {
|
||||
return Drupal.t('Off by default with opt in');
|
||||
}
|
||||
});
|
||||
|
||||
$('#edit-linktracking').drupalSetSummary(function (context) {
|
||||
var vals = [];
|
||||
if ($('input#edit-piwik-trackmailto', context).is(':checked')) {
|
||||
vals.push(Drupal.t('Mailto links'));
|
||||
}
|
||||
if ($('input#edit-piwik-trackfiles', context).is(':checked')) {
|
||||
vals.push(Drupal.t('Outbound links'));
|
||||
vals.push(Drupal.t('Downloads'));
|
||||
}
|
||||
if ($('input#edit-piwik-trackcolorbox', context).is(':checked')) {
|
||||
vals.push(Drupal.t('Colorbox'));
|
||||
}
|
||||
if (!vals.length) {
|
||||
return Drupal.t('Not tracked');
|
||||
}
|
||||
return Drupal.t('@items enabled', {'@items': vals.join(', ')});
|
||||
});
|
||||
|
||||
$('#edit-messagetracking').drupalSetSummary(function (context) {
|
||||
var vals = [];
|
||||
$('input[type="checkbox"]:checked', context).each(function () {
|
||||
vals.push($.trim($(this).next('label').text()));
|
||||
});
|
||||
if (!vals.length) {
|
||||
return Drupal.t('Not tracked');
|
||||
}
|
||||
return Drupal.t('@items enabled', {'@items': vals.join(', ')});
|
||||
});
|
||||
|
||||
$('#edit-search').drupalSetSummary(function (context) {
|
||||
var vals = [];
|
||||
if ($('input#edit-piwik-site-search', context).is(':checked')) {
|
||||
vals.push(Drupal.t('Site search'));
|
||||
}
|
||||
if (!vals.length) {
|
||||
return Drupal.t('Not tracked');
|
||||
}
|
||||
return Drupal.t('@items enabled', {'@items': vals.join(', ')});
|
||||
});
|
||||
|
||||
$('#edit-domain-tracking').drupalSetSummary(function (context) {
|
||||
var $radio = $('input[name="piwik_domain_mode"]:checked', context);
|
||||
if ($radio.val() === '0') {
|
||||
return Drupal.t('A single domain');
|
||||
}
|
||||
else if ($radio.val() === '1') {
|
||||
return Drupal.t('One domain with multiple subdomains');
|
||||
}
|
||||
});
|
||||
|
||||
$('#edit-privacy').drupalSetSummary(function (context) {
|
||||
var vals = [];
|
||||
if ($('input#edit-piwik-privacy-donottrack', context).is(':checked')) {
|
||||
vals.push(Drupal.t('Universal web tracking opt-out'));
|
||||
}
|
||||
if (!vals.length) {
|
||||
return Drupal.t('No privacy');
|
||||
}
|
||||
return Drupal.t('@items enabled', {'@items': vals.join(', ')});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery, window);
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @file
|
||||
* Attaches several event listener to a web page.
|
||||
*/
|
||||
|
||||
(function ($, drupalSettings) {
|
||||
|
||||
"use strict";
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
// Attach mousedown, keyup, touchstart events to document only and catch
|
||||
// clicks on all elements.
|
||||
$(document.body).bind("mousedown keyup touchstart", function (event) {
|
||||
|
||||
// Catch the closest surrounding link of a clicked element.
|
||||
$(event.target).closest("a,area").each(function () {
|
||||
|
||||
if (drupalSettings.piwik.trackMailto && $(this).is("a[href^='mailto:'],area[href^='mailto:']")) {
|
||||
// Mailto link clicked.
|
||||
_paq.push(["trackEvent", "Mails", "Click", this.href.substring(7)]);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// Colorbox: This event triggers when the transition has completed and the
|
||||
// newly loaded content has been revealed.
|
||||
if (drupalSettings.piwik.trackColorbox) {
|
||||
$(document).bind("cbox_complete", function () {
|
||||
var href = $.colorbox.element().attr("href");
|
||||
if (href) {
|
||||
_paq.push(["setCustomUrl", href]);
|
||||
_paq.push(["trackPageView"]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(jQuery, drupalSettings);
|
||||
@@ -0,0 +1,68 @@
|
||||
id: d6_piwik_settings
|
||||
label: Piwik 6 configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
source:
|
||||
plugin: variable
|
||||
variables:
|
||||
- piwik_cache
|
||||
- piwik_codesnippet_after
|
||||
- piwik_codesnippet_before
|
||||
- piwik_custom
|
||||
- piwik_domain_mode
|
||||
- piwik_pages
|
||||
- piwik_privacy_donottrack
|
||||
- piwik_roles
|
||||
- piwik_site_id
|
||||
- piwik_site_search
|
||||
- piwik_trackmessages
|
||||
- piwik_track
|
||||
- piwik_trackfiles_extensions
|
||||
- piwik_trackmailto
|
||||
- piwik_trackuserid
|
||||
- piwik_translation_set
|
||||
- piwik_url_http
|
||||
- piwik_url_https
|
||||
- piwik_visibility_pages
|
||||
- piwik_visibility_roles
|
||||
- piwik_page_title_hierarchy
|
||||
- piwik_page_title_hierarchy_exclude_home
|
||||
process:
|
||||
cache: piwik_cache
|
||||
'codesnippet/after': piwik_codesnippet_after
|
||||
'codesnippet/before': piwik_codesnippet_before
|
||||
domain_mode: piwik_domain_mode
|
||||
'privacy/donottrack': piwik_privacy_donottrack
|
||||
site_id: piwik_site_id
|
||||
'track/files': piwik_track
|
||||
'track/files_extensions': piwik_trackfiles_extensions
|
||||
'track/mailto': piwik_trackmailto
|
||||
'track/messages': piwik_trackmessages
|
||||
'track/site_search': piwik_site_search
|
||||
'track/userid': piwik_trackuserid
|
||||
'translation_set': piwik_translation_set
|
||||
url_http: piwik_url_http
|
||||
url_https: piwik_url_https
|
||||
'visibility/user_account_mode': piwik_custom
|
||||
'visibility/request_path_mode': piwik_visibility_pages
|
||||
'visibility/request_path_pages':
|
||||
plugin: piwik_visibility_pages
|
||||
source:
|
||||
- piwik_visibility_pages
|
||||
- piwik_pages
|
||||
# If Piwik uses PHP visibility, don't migrate it unless the PHP module is
|
||||
# enabled.
|
||||
skip_php: true
|
||||
'visibility/user_role_mode': piwik_visibility_roles
|
||||
'visibility/user_role_roles':
|
||||
plugin: piwik_visibility_roles
|
||||
source:
|
||||
- piwik_roles
|
||||
page_title_hierarchy: piwik_page_title_hierarchy
|
||||
page_title_hierarchy_exclude_home: piwik_page_title_hierarchy_exclude_home
|
||||
destination:
|
||||
plugin: config
|
||||
config_name: piwik.settings
|
||||
migration_dependencies:
|
||||
optional:
|
||||
- d6_user_role
|
||||
@@ -0,0 +1,24 @@
|
||||
id: d6_piwik_user_settings
|
||||
label: Piwik user settings
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
source:
|
||||
plugin: d6_user
|
||||
constants:
|
||||
key: user_account_users
|
||||
module: piwik
|
||||
process:
|
||||
uid: uid
|
||||
key: 'constants/key'
|
||||
module: 'constants/module'
|
||||
settings:
|
||||
# Based on skip_row_if_not_set
|
||||
plugin: piwik_skip_row_if_not_set
|
||||
key: custom
|
||||
module: piwik
|
||||
source: data
|
||||
destination:
|
||||
plugin: user_data
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_user
|
||||
@@ -0,0 +1,75 @@
|
||||
id: d7_piwik_settings
|
||||
label: Piwik 7 configuration
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
source:
|
||||
plugin: variable
|
||||
variables:
|
||||
- piwik_cache
|
||||
- piwik_codesnippet_after
|
||||
- piwik_codesnippet_before
|
||||
- piwik_custom
|
||||
- piwik_custom_var
|
||||
- piwik_domain_mode
|
||||
- piwik_pages
|
||||
- piwik_privacy_donottrack
|
||||
- piwik_roles
|
||||
- piwik_site_id
|
||||
- piwik_site_search
|
||||
- piwik_track
|
||||
- piwik_trackcolorbox
|
||||
- piwik_trackfiles_extensions
|
||||
- piwik_trackmailto
|
||||
- piwik_trackmessages
|
||||
- piwik_trackuserid
|
||||
- piwik_translation_set
|
||||
- piwik_url_http
|
||||
- piwik_url_https
|
||||
- piwik_visibility_pages
|
||||
- piwik_visibility_roles
|
||||
- piwik_page_title_hierarchy
|
||||
- piwik_page_title_hierarchy_exclude_home
|
||||
process:
|
||||
cache: piwik_cache
|
||||
'codesnippet/after': piwik_codesnippet_after
|
||||
'codesnippet/before': piwik_codesnippet_before
|
||||
'custom/variable':
|
||||
plugin: piwik_custom_vars
|
||||
source:
|
||||
- piwik_custom_var
|
||||
domain_mode: piwik_domain_mode
|
||||
'privacy/donottrack': piwik_privacy_donottrack
|
||||
site_id: piwik_site_id
|
||||
'track/colorbox': piwik_trackcolorbox
|
||||
'track/files': piwik_track
|
||||
'track/files_extensions': piwik_trackfiles_extensions
|
||||
'track/mailto': piwik_trackmailto
|
||||
'track/messages': piwik_trackmessages
|
||||
'track/site_search': piwik_site_search
|
||||
'track/userid': piwik_trackuserid
|
||||
'translation_set': piwik_translation_set
|
||||
url_http: piwik_url_http
|
||||
url_https: piwik_url_https
|
||||
'visibility/user_account_mode': piwik_custom
|
||||
'visibility/request_path_mode': piwik_visibility_pages
|
||||
'visibility/request_path_pages':
|
||||
plugin: piwik_visibility_pages
|
||||
source:
|
||||
- piwik_visibility_pages
|
||||
- piwik_pages
|
||||
# If Piwik uses PHP visibility, don't migrate it unless the PHP module is
|
||||
# enabled.
|
||||
skip_php: true
|
||||
'visibility/user_role_mode': piwik_visibility_roles
|
||||
'visibility/user_role_roles':
|
||||
plugin: piwik_visibility_roles
|
||||
source:
|
||||
- piwik_roles
|
||||
page_title_hierarchy: piwik_page_title_hierarchy
|
||||
page_title_hierarchy_exclude_home: piwik_page_title_hierarchy_exclude_home
|
||||
destination:
|
||||
plugin: config
|
||||
config_name: piwik.settings
|
||||
migration_dependencies:
|
||||
optional:
|
||||
- d7_user_role
|
||||
@@ -0,0 +1,24 @@
|
||||
id: d7_piwik_user_settings
|
||||
label: Piwik user settings
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
source:
|
||||
plugin: d7_user
|
||||
constants:
|
||||
key: user_account_users
|
||||
module: piwik
|
||||
process:
|
||||
uid: uid
|
||||
key: 'constants/key'
|
||||
module: 'constants/module'
|
||||
settings:
|
||||
# Based on skip_row_if_not_set
|
||||
plugin: piwik_skip_row_if_not_set
|
||||
key: custom
|
||||
module: piwik
|
||||
source: data
|
||||
destination:
|
||||
plugin: user_data
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d7_user
|
||||
@@ -0,0 +1,15 @@
|
||||
name: 'Piwik Web Analytics'
|
||||
type: module
|
||||
description: "Adds Piwik javascript tracking code to all your site's pages."
|
||||
package: Statistics
|
||||
# core: 8.x
|
||||
configure: piwik.admin_settings_form
|
||||
test_dependencies:
|
||||
- php:php
|
||||
- token:token
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-11-11
|
||||
version: '8.x-1.2'
|
||||
core: '8.x'
|
||||
project: 'piwik'
|
||||
datestamp: 1510404815
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation file for Piwik - Web analytics module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*
|
||||
* Remove cache directory if module is uninstalled.
|
||||
*/
|
||||
function piwik_uninstall() {
|
||||
piwik_clear_js_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function piwik_requirements($phase) {
|
||||
$requirements = [];
|
||||
|
||||
if ($phase == 'runtime') {
|
||||
$config = \Drupal::config('piwik.settings');
|
||||
|
||||
// Raise warning if Piwik user account has not been set yet.
|
||||
if (!preg_match('/^\d{1,}$/', $config->get('site_id'))) {
|
||||
$requirements['piwik_site_id'] = [
|
||||
'title' => t('Piwik module'),
|
||||
'description' => t('Piwik module has not been configured yet. Please configure its settings from the <a href=":url">Piwik settings page</a>.', [':url' => Url::fromRoute('piwik.admin_settings_form')->toString()]),
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
'value' => t('Not configured'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
piwik:
|
||||
version: VERSION
|
||||
js:
|
||||
js/piwik.js: {}
|
||||
dependencies:
|
||||
- core/jquery
|
||||
- core/drupalSettings
|
||||
|
||||
piwik.admin:
|
||||
version: VERSION
|
||||
js:
|
||||
js/piwik.admin.js: {}
|
||||
dependencies:
|
||||
- core/jquery
|
||||
- core/drupal
|
||||
@@ -0,0 +1,5 @@
|
||||
piwik.admin_settings_form:
|
||||
title: 'Piwik'
|
||||
parent: system.admin_config_system
|
||||
description: 'Configure the settings used to generate your Piwik tracking code.'
|
||||
route_name: piwik.admin_settings_form
|
||||
@@ -0,0 +1,4 @@
|
||||
piwik.admin_settings_form_tab:
|
||||
route_name: piwik.admin_settings_form
|
||||
title: Settings
|
||||
base_route: piwik.admin_settings_form
|
||||
@@ -0,0 +1,727 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Drupal Module: Piwik.
|
||||
*
|
||||
* Adds the required Javascript to all your Drupal pages to allow tracking by
|
||||
* the Piwik statistics package.
|
||||
*
|
||||
* @author: Alexander Hass <http://drupal.org/user/85918>
|
||||
*/
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\NodeInterface;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Drupal\piwik\Component\Render\PiwikJavaScriptSnippet;
|
||||
|
||||
/**
|
||||
* Define the default file extension list that should be tracked as download.
|
||||
*/
|
||||
define('PIWIK_TRACKFILES_EXTENSIONS', '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc(x|m)?|dot(x|m)?|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt(x|m)?|pot(x|m)?|pps(x|m)?|ppam|sld(x|m)?|thmx|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls(x|m|b)?|xlt(x|m)|xlam|xml|z|zip');
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function piwik_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'piwik.admin_settings_form':
|
||||
return t('<a href=":pk_url">Piwik - Web analytics</a> is an open source (GPL license) web analytics software. It gives interesting reports on your website visitors, your popular pages, the search engines keywords they used, the language they speak... and so much more. Piwik aims to be an open source alternative to Google Analytics.', [':pk_url' => 'http://www.piwik.org/']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_page_attachments().
|
||||
*
|
||||
* Insert JavaScript to the appropriate scope/region of the page.
|
||||
*/
|
||||
function piwik_page_attachments(array &$page) {
|
||||
$account = \Drupal::currentUser();
|
||||
$config = \Drupal::config('piwik.settings');
|
||||
$id = $config->get('site_id');
|
||||
$request = \Drupal::request();
|
||||
|
||||
// Add module cache tags.
|
||||
$page['#cache']['tags'] = Cache::mergeTags(isset($page['#cache']['tags']) ? $page['#cache']['tags'] : [], $config->getCacheTags());
|
||||
|
||||
// Get page http status code for visibility filtering.
|
||||
$status = NULL;
|
||||
if ($exception = $request->attributes->get('exception')) {
|
||||
$status = $exception->getStatusCode();
|
||||
}
|
||||
$trackable_status_codes = [
|
||||
// "Forbidden" status code.
|
||||
'403',
|
||||
// "Not Found" status code.
|
||||
'404',
|
||||
];
|
||||
|
||||
// 1. Check if the piwik account number has a valid value.
|
||||
// 2. Track page views based on visibility value.
|
||||
// 3. Check if we should track the currently active user's role.
|
||||
if (preg_match('/^\d{1,}$/', $id) && (_piwik_visibility_pages() || in_array($status, $trackable_status_codes)) && _piwik_visibility_user($account)) {
|
||||
|
||||
$url_http = $config->get('url_http');
|
||||
$url_https = $config->get('url_https');
|
||||
|
||||
$set_custom_url = '';
|
||||
$set_document_title = '';
|
||||
$set_custom_data = [];
|
||||
|
||||
// Add link tracking.
|
||||
$link_settings = [];
|
||||
$link_settings['trackMailto'] = $config->get('track.mailto');
|
||||
|
||||
if ((\Drupal::moduleHandler()->moduleExists('colorbox')) && $track_colorbox = $config->get('track.colorbox')) {
|
||||
$link_settings['trackColorbox'] = $track_colorbox;
|
||||
}
|
||||
|
||||
$page['#attached']['drupalSettings']['piwik'] = $link_settings;
|
||||
$page['#attached']['library'][] = 'piwik/piwik';
|
||||
|
||||
// Piwik can show a tree view of page titles that represents the site
|
||||
// structure if setDocumentTitle() provides the page titles as a "/"
|
||||
// delimited list. This may makes it easier to browse through the statistics
|
||||
// of page titles on larger sites.
|
||||
if ($config->get('page_title_hierarchy') == TRUE) {
|
||||
$titles = _piwik_get_hierarchy_titles();
|
||||
|
||||
// Remove all empty titles.
|
||||
$titles = array_filter($titles);
|
||||
|
||||
if (!empty($titles)) {
|
||||
// Encode title, at least to keep "/" intact.
|
||||
$titles = array_map('rawurlencode', $titles);
|
||||
|
||||
$set_document_title = Json::encode(implode('/', $titles));
|
||||
}
|
||||
}
|
||||
|
||||
// Add messages tracking.
|
||||
$message_events = '';
|
||||
if ($message_types = $config->get('track.messages')) {
|
||||
$message_types = array_values(array_filter($message_types));
|
||||
$status_heading = [
|
||||
'status' => t('Status message'),
|
||||
'warning' => t('Warning message'),
|
||||
'error' => t('Error message'),
|
||||
];
|
||||
|
||||
foreach (drupal_get_messages(NULL, FALSE) as $type => $messages) {
|
||||
// Track only the selected message types.
|
||||
if (in_array($type, $message_types)) {
|
||||
foreach ($messages as $message) {
|
||||
$message_events .= '_paq.push(["trackEvent", ' . Json::encode(t('Messages')) . ', ' . Json::encode($status_heading[$type]) . ', ' . Json::encode(strip_tags($message)) . ']);';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this node is a translation of another node, pass the original
|
||||
// node instead.
|
||||
if (\Drupal::moduleHandler()->moduleExists('content_translation') && $config->get('translation_set')) {
|
||||
// Check if we have a node object, it has translation enabled, and its
|
||||
// language code does not match its source language code.
|
||||
if ($request->attributes->has('node')) {
|
||||
$node = $request->attributes->get('node');
|
||||
if ($node instanceof NodeInterface && \Drupal::service('entity.repository')->getTranslationFromContext($node) !== $node->getUntranslated()) {
|
||||
$set_custom_url = Json::encode(Url::fromRoute('entity.node.canonical', ['node' => $node->id()], ['language' => $node->getUntranslated()->language()])->toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track access denied (403) and file not found (404) pages.
|
||||
if ($status == '403') {
|
||||
$set_document_title = '"403/URL = " + encodeURIComponent(document.location.pathname+document.location.search) + "/From = " + encodeURIComponent(document.referrer)';
|
||||
}
|
||||
elseif ($status == '404') {
|
||||
$set_document_title = '"404/URL = " + encodeURIComponent(document.location.pathname+document.location.search) + "/From = " + encodeURIComponent(document.referrer)';
|
||||
}
|
||||
|
||||
// #2693595: User has entered an invalid login and clicked on forgot
|
||||
// password link. This link contains the username or email address and may
|
||||
// get send to Google if we do not override it. Override only if 'name'
|
||||
// query param exists. Last custom url condition, this need to win.
|
||||
//
|
||||
// URLs to protect are:
|
||||
// - user/password?name=username
|
||||
// - user/password?name=foo@example.com
|
||||
if (\Drupal::routeMatch()->getRouteName() == 'user.pass' && $request->query->has('name')) {
|
||||
$set_custom_url = Json::encode(Url::fromRoute('user.pass')->toString());
|
||||
}
|
||||
|
||||
// Add custom variables.
|
||||
$piwik_custom_vars = $config->get('custom.variable');
|
||||
$custom_variable = '';
|
||||
for ($i = 1; $i < 6; $i++) {
|
||||
$custom_var_name = !empty($piwik_custom_vars[$i]['name']) ? $piwik_custom_vars[$i]['name'] : '';
|
||||
if (!empty($custom_var_name)) {
|
||||
$custom_var_value = !empty($piwik_custom_vars[$i]['value']) ? $piwik_custom_vars[$i]['value'] : '';
|
||||
$custom_var_scope = !empty($piwik_custom_vars[$i]['scope']) ? $piwik_custom_vars[$i]['scope'] : 'visit';
|
||||
|
||||
$types = [];
|
||||
if ($request->attributes->has('node')) {
|
||||
$node = $request->attributes->get('node');
|
||||
if ($node instanceof NodeInterface) {
|
||||
$types += ['node' => $node];
|
||||
}
|
||||
}
|
||||
$custom_var_name = \Drupal::token()->replace($custom_var_name, $types, ['clear' => TRUE]);
|
||||
$custom_var_value = \Drupal::token()->replace($custom_var_value, $types, ['clear' => TRUE]);
|
||||
|
||||
// Suppress empty custom names and/or variables.
|
||||
if (!Unicode::strlen(trim($custom_var_name)) || !Unicode::strlen(trim($custom_var_value))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Custom variables names and values are limited to 200 characters in
|
||||
// length. It is recommended to store values that are as small as
|
||||
// possible to ensure that the Piwik Tracking request URL doesn't go
|
||||
// over the URL limit for the webserver or browser.
|
||||
$custom_var_name = rtrim(substr($custom_var_name, 0, 200));
|
||||
$custom_var_value = rtrim(substr($custom_var_value, 0, 200));
|
||||
|
||||
// Add variables to tracker.
|
||||
$custom_variable .= '_paq.push(["setCustomVariable", ' . Json::encode($i) . ', ' . Json::encode($custom_var_name) . ', ' . Json::encode($custom_var_value) . ', ' . Json::encode($custom_var_scope) . ']);';
|
||||
}
|
||||
}
|
||||
|
||||
// Add any custom code snippets if specified.
|
||||
$codesnippet_before = $config->get('codesnippet.before');
|
||||
$codesnippet_after = $config->get('codesnippet.after');
|
||||
|
||||
// Build tracker code.
|
||||
// @see http://piwik.org/docs/javascript-tracking/#toc-asynchronous-tracking
|
||||
$script = 'var _paq = _paq || [];';
|
||||
$script .= '(function(){';
|
||||
$script .= 'var u=(("https:" == document.location.protocol) ? "' . UrlHelper::filterBadProtocol($url_https) . '" : "' . UrlHelper::filterBadProtocol($url_http) . '");';
|
||||
$script .= '_paq.push(["setSiteId", ' . Json::encode($id) . ']);';
|
||||
$script .= '_paq.push(["setTrackerUrl", u+"piwik.php"]);';
|
||||
|
||||
// Track logged in users across all devices.
|
||||
if ($config->get('track.userid') && $account->isAuthenticated()) {
|
||||
$script .= '_paq.push(["setUserId", ' . Json::encode(piwik_user_id_hash($account->id())) . ']);';
|
||||
}
|
||||
|
||||
// Set custom data.
|
||||
if (!empty($set_custom_data)) {
|
||||
foreach ($set_custom_data as $custom_data) {
|
||||
$script .= '_paq.push(["setCustomData", ' . $custom_data . ']);';
|
||||
}
|
||||
}
|
||||
// Set custom url.
|
||||
if (!empty($set_custom_url)) {
|
||||
$script .= '_paq.push(["setCustomUrl", ' . $set_custom_url . ']);';
|
||||
}
|
||||
// Set custom document title.
|
||||
if (!empty($set_document_title)) {
|
||||
$script .= '_paq.push(["setDocumentTitle", ' . $set_document_title . ']);';
|
||||
}
|
||||
|
||||
// Custom file download extensions.
|
||||
if ($config->get('track.files') && !($config->get('track.files_extensions') == PIWIK_TRACKFILES_EXTENSIONS)) {
|
||||
$script .= '_paq.push(["setDownloadExtensions", ' . Json::encode($config->get('track.files_extensions')) . ']);';
|
||||
}
|
||||
|
||||
// Disable tracking for visitors who have opted out from tracking via DNT
|
||||
// (Do-Not-Track) header.
|
||||
if ($config->get('privacy.donottrack')) {
|
||||
$script .= '_paq.push(["setDoNotTrack", 1]);';
|
||||
}
|
||||
|
||||
// Domain tracking type.
|
||||
global $cookie_domain;
|
||||
$domain_mode = $config->get('domain_mode');
|
||||
|
||||
// Per RFC 2109, cookie domains must contain at least one dot other than the
|
||||
// first. For hosts such as 'localhost' or IP Addresses we don't set a
|
||||
// cookie domain.
|
||||
if ($domain_mode == 1 && count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) {
|
||||
$script .= '_paq.push(["setCookieDomain", ' . Json::encode($cookie_domain) . ']);';
|
||||
}
|
||||
|
||||
// Ordering $custom_variable before $codesnippet_before allows users to add
|
||||
// custom code snippets that may use deleteCustomVariable() and/or
|
||||
// getCustomVariable().
|
||||
if (!empty($custom_variable)) {
|
||||
$script .= $custom_variable;
|
||||
}
|
||||
if (!empty($codesnippet_before)) {
|
||||
$script .= $codesnippet_before;
|
||||
}
|
||||
|
||||
// Site search tracking support.
|
||||
// NOTE: It's recommended not to call trackPageView() on the Site Search
|
||||
// Result page.
|
||||
if (\Drupal::moduleHandler()->moduleExists('search') && $config->get('track.site_search') && (strpos(\Drupal::routeMatch()->getRouteName(), 'search.view') === 0) && $keys = ($request->query->has('keys') ? trim($request->get('keys')) : '')) {
|
||||
// Parameters:
|
||||
// 1. Search keyword searched for. Example: "Banana"
|
||||
// 2. Search category selected in your search engine. If you do not need
|
||||
// this, set to false. Example: "Organic Food"
|
||||
// 3. Number of results on the Search results page. Zero indicates a
|
||||
// 'No Result Search Keyword'. Set to false if you don't know.
|
||||
//
|
||||
// hook_preprocess_search_results() is not executed if search result is
|
||||
// empty. Make sure the counter is set to 0 if there are no results.
|
||||
$script .= '_paq.push(["trackSiteSearch", ' . Json::encode($keys) . ', false, (window.piwik_search_results) ? window.piwik_search_results : 0]);';
|
||||
}
|
||||
else {
|
||||
$script .= '_paq.push(["trackPageView"]);';
|
||||
}
|
||||
|
||||
// Add link tracking.
|
||||
if ($config->get('track.files')) {
|
||||
// Disable tracking of links with ".no-tracking" and ".colorbox" classes.
|
||||
$ignore_classes = [
|
||||
'no-tracking',
|
||||
'colorbox',
|
||||
];
|
||||
// Disable the download & outlink tracking for specific CSS classes.
|
||||
// Custom code snippets with 'setIgnoreClasses' will override the value.
|
||||
// @see http://developer.piwik.org/api-reference/tracking-javascript#disable-the-download-amp-outlink-tracking-for-specific-css-classes
|
||||
$script .= '_paq.push(["setIgnoreClasses", ' . Json::encode($ignore_classes) . ']);';
|
||||
|
||||
// Enable download & outlink link tracking.
|
||||
$script .= '_paq.push(["enableLinkTracking"]);';
|
||||
}
|
||||
|
||||
if (!empty($message_events)) {
|
||||
$script .= $message_events;
|
||||
}
|
||||
if (!empty($codesnippet_after)) {
|
||||
$script .= $codesnippet_after;
|
||||
}
|
||||
|
||||
$script .= 'var d=document,';
|
||||
$script .= 'g=d.createElement("script"),';
|
||||
$script .= 's=d.getElementsByTagName("script")[0];';
|
||||
$script .= 'g.type="text/javascript";';
|
||||
$script .= 'g.defer=true;';
|
||||
$script .= 'g.async=true;';
|
||||
|
||||
// Should a local cached copy of the tracking code be used?
|
||||
if ($config->get('cache') && $url = _piwik_cache($url_http . 'piwik.js')) {
|
||||
// A dummy query-string is added to filenames, to gain control over
|
||||
// browser-caching. The string changes on every update or full cache
|
||||
// flush, forcing browsers to load a new copy of the files, as the
|
||||
// URL changed.
|
||||
$query_string = '?' . (\Drupal::state()->get('system.css_js_query_string') ?: '0');
|
||||
|
||||
$script .= 'g.src="' . $url . $query_string . '";';
|
||||
}
|
||||
else {
|
||||
$script .= 'g.src=u+"piwik.js";';
|
||||
}
|
||||
|
||||
$script .= 's.parentNode.insertBefore(g,s);';
|
||||
$script .= '})();';
|
||||
|
||||
// Add tracker code.
|
||||
$page['#attached']['html_head'][] = [
|
||||
[
|
||||
'#tag' => 'script',
|
||||
'#value' => new PiwikJavaScriptSnippet($script),
|
||||
],
|
||||
'piwik_tracking_script',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate user id hash to implement USER_ID.
|
||||
*
|
||||
* The USER_ID value should be a unique, persistent, and non-personally
|
||||
* identifiable string identifier that represents a user or signed-in
|
||||
* account across devices.
|
||||
*
|
||||
* @param int $uid
|
||||
* User id.
|
||||
*
|
||||
* @return string
|
||||
* User id hash.
|
||||
*/
|
||||
function piwik_user_id_hash($uid) {
|
||||
return Crypt::hmacBase64($uid, \Drupal::service('private_key')->get() . Settings::getHashSalt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_extra_field_info().
|
||||
*/
|
||||
function piwik_entity_extra_field_info() {
|
||||
$extra['user']['user']['form']['piwik'] = [
|
||||
'label' => t('Piwik settings'),
|
||||
'description' => t('Piwik module form element.'),
|
||||
'weight' => 3,
|
||||
];
|
||||
|
||||
return $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*
|
||||
* Allow users to decide if tracking code will be added to pages or not.
|
||||
*/
|
||||
function piwik_form_user_form_alter(&$form, FormStateInterface $form_state) {
|
||||
$config = \Drupal::config('piwik.settings');
|
||||
$account = $form_state->getFormObject()->getEntity();
|
||||
|
||||
if ($account->hasPermission('opt-in or out of piwik tracking') && ($visibility_users = $config->get('visibility.user_account_mode')) != 0 && _piwik_visibility_roles($account)) {
|
||||
$account_data_piwik = \Drupal::service('user.data')->get('piwik', $account->id());
|
||||
|
||||
$form['piwik'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => t('Piwik settings'),
|
||||
'#weight' => 3,
|
||||
'#open' => TRUE,
|
||||
];
|
||||
|
||||
switch ($visibility_users) {
|
||||
case 1:
|
||||
$description = t('Users are tracked by default, but you are able to opt out.');
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$description = t('Users are <em>not</em> tracked by default, but you are able to opt in.');
|
||||
break;
|
||||
}
|
||||
|
||||
// Disable tracking for visitors who have opted out from tracking via DNT
|
||||
// (Do-Not-Track) header.
|
||||
$disabled = FALSE;
|
||||
if ($config->get('privacy.donottrack') && !empty($_SERVER['HTTP_DNT'])) {
|
||||
$disabled = TRUE;
|
||||
|
||||
// Override settings value.
|
||||
$account_data_piwik['users'] = FALSE;
|
||||
|
||||
$description .= '<span class="admin-missing">';
|
||||
$description .= ' ' . t('You have opted out from tracking via browser privacy settings.');
|
||||
$description .= '</span>';
|
||||
}
|
||||
|
||||
$form['piwik']['user_account_users'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enable user tracking'),
|
||||
'#description' => $description,
|
||||
'#default_value' => isset($account_data_piwik['user_account_users']) ? $account_data_piwik['user_account_users'] : ($visibility_users == 1),
|
||||
'#disabled' => $disabled,
|
||||
];
|
||||
|
||||
// hook_user_update() is missing in D8, add custom submit handler.
|
||||
$form['actions']['submit']['#submit'][] = 'piwik_user_profile_form_submit';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for user profile form to save the Piwik setting.
|
||||
*/
|
||||
function piwik_user_profile_form_submit($form, FormStateInterface $form_state) {
|
||||
$account = $form_state->getFormObject()->getEntity();
|
||||
if ($account->id() && $form_state->hasValue('user_account_users')) {
|
||||
\Drupal::service('user.data')->set('piwik', $account->id(), 'user_account_users', (int) $form_state->getValue('user_account_users'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_cron().
|
||||
*/
|
||||
function piwik_cron() {
|
||||
$config = \Drupal::config('piwik.settings');
|
||||
|
||||
// Regenerate the piwik.js every day.
|
||||
if (REQUEST_TIME - \Drupal::state()->get('piwik.last_cache') >= 86400 && $config->get('cache')) {
|
||||
_piwik_cache($config->get('url_http') . 'piwik.js', TRUE);
|
||||
\Drupal::state()->set('piwik.last_cache', REQUEST_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_item_list__search_results().
|
||||
*
|
||||
* Collects and adds the number of search results to the head.
|
||||
*/
|
||||
function piwik_preprocess_item_list__search_results(&$variables) {
|
||||
$config = \Drupal::config('piwik.settings');
|
||||
|
||||
// Only run on search results list.
|
||||
if ($config->get('track.site_search')) {
|
||||
// There is no search result $variable available that hold the number of
|
||||
// items found. The variable $variables['items'] has the current page items
|
||||
// only. But the pager item mumber can tell the number of search results.
|
||||
global $pager_total_items;
|
||||
|
||||
$variables['#attached']['html_head'][] = [
|
||||
[
|
||||
'#tag' => 'script',
|
||||
'#value' => 'window.piwik_search_results = ' . intval($pager_total_items[0]) . ';',
|
||||
'#weight' => JS_LIBRARY - 1,
|
||||
],
|
||||
'piwik_search_script',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download/Synchronize/Cache tracking code file locally.
|
||||
*
|
||||
* @param string $location
|
||||
* The full URL to the external javascript file.
|
||||
* @param bool $synchronize
|
||||
* Synchronize to local cache if remote file has changed.
|
||||
*
|
||||
* @return mixed
|
||||
* The path to the local javascript file on success, boolean FALSE on failure.
|
||||
*/
|
||||
function _piwik_cache($location, $synchronize = FALSE) {
|
||||
$path = 'public://piwik';
|
||||
$file_destination = $path . '/' . basename($location);
|
||||
|
||||
if (!file_exists($file_destination) || $synchronize) {
|
||||
// Download the latest tracking code.
|
||||
try {
|
||||
$data = \Drupal::httpClient()
|
||||
->get($location)
|
||||
->getBody(TRUE);
|
||||
|
||||
if (file_exists($file_destination)) {
|
||||
// Synchronize tracking code and and replace local file if outdated.
|
||||
$data_hash_local = Crypt::hashBase64(file_get_contents($file_destination));
|
||||
$data_hash_remote = Crypt::hashBase64($data);
|
||||
// Check that the files directory is writable.
|
||||
if ($data_hash_local != $data_hash_remote && file_prepare_directory($path)) {
|
||||
// Save updated tracking code file to disk.
|
||||
file_unmanaged_save_data($data, $file_destination, FILE_EXISTS_REPLACE);
|
||||
// Based on Drupal Core class AssetDumper.
|
||||
if (extension_loaded('zlib') && \Drupal::config('system.performance')->get('js.gzip')) {
|
||||
file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $file_destination . '.gz', FILE_EXISTS_REPLACE);
|
||||
}
|
||||
\Drupal::logger('piwik')->info('Locally cached tracking code file has been updated.');
|
||||
|
||||
// Change query-strings on css/js files to enforce reload for all
|
||||
// users.
|
||||
_drupal_flush_css_js();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Check that the files directory is writable.
|
||||
if (file_prepare_directory($path, FILE_CREATE_DIRECTORY)) {
|
||||
// There is no need to flush JS here as core refreshes JS caches
|
||||
// automatically, if new files are added.
|
||||
file_unmanaged_save_data($data, $file_destination, FILE_EXISTS_REPLACE);
|
||||
// Based on Drupal Core class AssetDumper.
|
||||
if (extension_loaded('zlib') && \Drupal::config('system.performance')->get('js.gzip')) {
|
||||
file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $file_destination . '.gz', FILE_EXISTS_REPLACE);
|
||||
}
|
||||
\Drupal::logger('piwik')->info('Locally cached tracking code file has been saved.');
|
||||
|
||||
// Return the local JS file path.
|
||||
return file_url_transform_relative(file_create_url($file_destination));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (RequestException $exception) {
|
||||
watchdog_exception('piwik', $exception);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Return the local JS file path.
|
||||
return file_url_transform_relative(file_create_url($file_destination));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cached files and directory.
|
||||
*/
|
||||
function piwik_clear_js_cache() {
|
||||
$path = 'public://piwik';
|
||||
if (file_prepare_directory($path)) {
|
||||
file_scan_directory($path, '/.*/', ['callback' => 'file_unmanaged_delete']);
|
||||
\Drupal::service('file_system')->rmdir($path);
|
||||
|
||||
// Change query-strings on css/js files to enforce reload for all users.
|
||||
_drupal_flush_css_js();
|
||||
|
||||
\Drupal::logger('piwik')->info('Local cache has been purged.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracking visibility check for an user object.
|
||||
*
|
||||
* @param object $account
|
||||
* A user object containing an array of roles to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the current user is being tracked by Piwik, otherwise FALSE.
|
||||
*/
|
||||
function _piwik_visibility_user($account) {
|
||||
$config = \Drupal::config('piwik.settings');
|
||||
$enabled = FALSE;
|
||||
|
||||
// Is current user a member of a role that should be tracked?
|
||||
if (_piwik_visibility_roles($account)) {
|
||||
|
||||
// Use the user's block visibility setting, if necessary.
|
||||
if (($visibility_user_account_mode = $config->get('visibility.user_account_mode')) != 0) {
|
||||
$user_data_piwik = \Drupal::service('user.data')->get('piwik', $account->id());
|
||||
if ($account->id() && isset($user_data_piwik['user_account_users'])) {
|
||||
$enabled = $user_data_piwik['user_account_users'];
|
||||
}
|
||||
else {
|
||||
$enabled = ($visibility_user_account_mode == 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$enabled = TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracking visibility check for user roles.
|
||||
*
|
||||
* Based on visibility setting this function returns TRUE if Piwik code should
|
||||
* be added for the current role and otherwise FALSE.
|
||||
*
|
||||
* @param object $account
|
||||
* A user object containing an array of roles to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if JS code should be added for the current role and otherwise FALSE.
|
||||
*/
|
||||
function _piwik_visibility_roles($account) {
|
||||
$config = \Drupal::config('piwik.settings');
|
||||
$enabled = $visibility_user_role_mode = $config->get('visibility.user_role_mode');
|
||||
$user_role_roles = $config->get('visibility.user_role_roles');
|
||||
|
||||
if (count($user_role_roles) > 0) {
|
||||
// One or more roles are selected.
|
||||
foreach (array_values($account->getRoles()) as $user_role) {
|
||||
// Is the current user a member of one of these roles?
|
||||
if (in_array($user_role, $user_role_roles)) {
|
||||
// Current user is a member of a role that should be tracked/excluded
|
||||
// from tracking.
|
||||
$enabled = !$visibility_user_role_mode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No role is selected for tracking, therefore all roles should be tracked.
|
||||
$enabled = TRUE;
|
||||
}
|
||||
|
||||
return $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracking visibility check for pages.
|
||||
*
|
||||
* Based on visibility setting this function returns TRUE if JS code should
|
||||
* be added to the current page and otherwise FALSE.
|
||||
*/
|
||||
function _piwik_visibility_pages() {
|
||||
static $page_match;
|
||||
|
||||
// Cache visibility result if function is called more than once.
|
||||
if (!isset($page_match)) {
|
||||
$config = \Drupal::config('piwik.settings');
|
||||
$visibility_request_path_mode = $config->get('visibility.request_path_mode');
|
||||
$visibility_request_path_pages = $config->get('visibility.request_path_pages');
|
||||
|
||||
// Match path if necessary.
|
||||
if (!empty($visibility_request_path_pages)) {
|
||||
// Convert path to lowercase. This allows comparison of the same path
|
||||
// with different case. Ex: /Page, /page, /PAGE.
|
||||
$pages = Unicode::strtolower($visibility_request_path_pages);
|
||||
if ($visibility_request_path_mode < 2) {
|
||||
// Compare the lowercase path alias (if any) and internal path.
|
||||
$path = \Drupal::service('path.current')->getPath();
|
||||
$path_alias = Unicode::strtolower(\Drupal::service('path.alias_manager')->getAliasByPath($path));
|
||||
$page_match = \Drupal::service('path.matcher')->matchPath($path_alias, $pages) || (($path != $path_alias) && \Drupal::service('path.matcher')->matchPath($path, $pages));
|
||||
// When $visibility_request_path_mode has a value of 0, the tracking
|
||||
// code is displayed on all pages except those listed in $pages. When
|
||||
// set to 1, it is displayed only on those pages listed in $pages.
|
||||
$page_match = !($visibility_request_path_mode xor $page_match);
|
||||
}
|
||||
elseif (\Drupal::moduleHandler()->moduleExists('php')) {
|
||||
$page_match = php_eval($visibility_request_path_pages);
|
||||
}
|
||||
else {
|
||||
$page_match = FALSE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$page_match = TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
return $page_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the page titles trail for the current page.
|
||||
*
|
||||
* Based on menu_get_active_breadcrumb().
|
||||
*
|
||||
* @return array
|
||||
* All page titles, including current page.
|
||||
*/
|
||||
function _piwik_get_hierarchy_titles() {
|
||||
$titles = [];
|
||||
$path = Url::fromRoute('<current>')->toString();
|
||||
$config = \Drupal::config('system.site');
|
||||
$piwik_conf = \Drupal::config('piwik.settings');
|
||||
|
||||
// No breadcrumb for the front page.
|
||||
if (($path === Url::fromUserInput($config->get('page.front'))->toString())) {
|
||||
return $titles;
|
||||
}
|
||||
|
||||
// Load up the menu tree.
|
||||
// TODO: Check this is a sane approach.
|
||||
$menu_tree = \Drupal::menuTree();
|
||||
$menu_name = \Drupal::config('system.menu.main')->get('id');
|
||||
|
||||
// Build the typical default set of menu tree parameters.
|
||||
$parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
|
||||
|
||||
// Load the tree based on this set of parameters.
|
||||
$tree = $menu_tree->load($menu_name, $parameters);
|
||||
|
||||
// Transform the tree using the manipulators.
|
||||
$manipulators = [
|
||||
// Only show links that are accessible for the current user.
|
||||
['callable' => 'menu.default_tree_manipulators:checkAccess'],
|
||||
// Use the default sorting of menu links.
|
||||
['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
|
||||
// Fatten the menu.
|
||||
['callable' => 'menu.default_tree_manipulators:flatten'],
|
||||
];
|
||||
$tree = $menu_tree->transform($tree, $manipulators);
|
||||
|
||||
if (!empty($tree)) {
|
||||
foreach ($tree as $menu_item) {
|
||||
// If the item is in the active trail and we don't have a front page link
|
||||
// when we have set to exclude home from the breadcrumbs then add the
|
||||
// title.
|
||||
if ($menu_item->inActiveTrail && !($piwik_conf->get('page_title_hierarchy_exclude_home') && $menu_item->link->getRouteName() == '<front>')) {
|
||||
$titles[] = $menu_item->link->getTitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $titles;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
administer piwik:
|
||||
title: 'Administer Piwik'
|
||||
description: 'Perform maintenance tasks for Piwik.'
|
||||
opt-in or out of piwik tracking:
|
||||
title: 'Opt-in or out of tracking'
|
||||
description: 'Allow users to decide if tracking code will be added to pages or not.'
|
||||
use PHP for piwik tracking visibility:
|
||||
title: 'Use PHP for tracking visibility'
|
||||
description: 'Enter PHP code in the field for tracking visibility settings.'
|
||||
restrict access: true
|
||||
add JS snippets for piwik:
|
||||
title: 'Add JavaScript snippets'
|
||||
description: 'Enter JavaScript code snippets for advanced Piwik functionality.'
|
||||
restrict access: true
|
||||
@@ -0,0 +1,7 @@
|
||||
piwik.admin_settings_form:
|
||||
path: '/admin/config/system/piwik'
|
||||
defaults:
|
||||
_form: '\Drupal\piwik\Form\PiwikAdminSettingsForm'
|
||||
_title: 'Piwik'
|
||||
requirements:
|
||||
_permission: 'administer piwik'
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Builds placeholder replacement tokens for user-related data.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
|
||||
/**
|
||||
* Implements hook_token_info().
|
||||
*/
|
||||
function piwik_token_info() {
|
||||
$user['piwik-role-names'] = [
|
||||
'name' => t('User role names'),
|
||||
'description' => t('The role names the user account is a member of as comma separated list.'),
|
||||
'needs-data' => 'user',
|
||||
];
|
||||
$user['piwik-role-ids'] = [
|
||||
'name' => t('User role ids'),
|
||||
'description' => t('The role ids the user account is a member of as comma separated list.'),
|
||||
'needs-data' => 'user',
|
||||
];
|
||||
|
||||
return [
|
||||
'tokens' => ['user' => $user],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_tokens().
|
||||
*/
|
||||
function piwik_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
|
||||
$sanitize = !empty($options['sanitize']);
|
||||
$replacements = [];
|
||||
|
||||
if ($type == 'user' && !empty($data['user'])) {
|
||||
$account = $data['user'];
|
||||
|
||||
foreach ($tokens as $name => $original) {
|
||||
switch ($name) {
|
||||
// Basic user account information.
|
||||
case 'piwik-role-names':
|
||||
$names = implode(',', $account->getRoles());
|
||||
$replacements[$original] = $sanitize ? Html::escape($names) : $names;
|
||||
$bubbleable_metadata->addCacheableDependency($account);
|
||||
break;
|
||||
|
||||
case 'piwik-role-ids':
|
||||
$ids = implode(',', array_keys($account->getRoles()));
|
||||
$replacements[$original] = $sanitize ? Html::escape($ids) : $ids;
|
||||
$bubbleable_metadata->addCacheableDependency($account);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $replacements;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Component\Render;
|
||||
|
||||
use Drupal\Component\Render\MarkupInterface;
|
||||
|
||||
/**
|
||||
* Formats a string for JavaScript display.
|
||||
*/
|
||||
class PiwikJavaScriptSnippet implements MarkupInterface {
|
||||
|
||||
/**
|
||||
* The string to escape.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $string;
|
||||
|
||||
/**
|
||||
* Constructs an HtmlEscapedText object.
|
||||
*
|
||||
* @param string $string
|
||||
* The string to escape. This value will be cast to a string.
|
||||
*/
|
||||
public function __construct($string) {
|
||||
$this->string = (string) $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->string;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
return $this->__toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,717 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Form;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
|
||||
/**
|
||||
* Configure Piwik settings for this site.
|
||||
*/
|
||||
class PiwikAdminSettingsForm extends ConfigFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'piwik_admin_settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return ['piwik.settings'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$config = $this->config('piwik.settings');
|
||||
|
||||
$form['general'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('General settings'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
|
||||
$form['general']['piwik_site_id'] = [
|
||||
'#default_value' => $config->get('site_id'),
|
||||
'#description' => $this->t('The user account number is unique to the websites domain. Click the <strong>Settings</strong> link in your Piwik account, then the <strong>Websites</strong> tab and enter the appropriate site <strong>ID</strong> into this field.'),
|
||||
'#maxlength' => 20,
|
||||
'#required' => TRUE,
|
||||
'#size' => 15,
|
||||
'#title' => $this->t('Piwik site ID'),
|
||||
'#type' => 'textfield',
|
||||
];
|
||||
$form['general']['piwik_url_http'] = [
|
||||
'#default_value' => $config->get('url_http'),
|
||||
'#description' => $this->t('The URL to your Piwik base directory. Example: "http://www.example.com/piwik/".'),
|
||||
'#maxlength' => 255,
|
||||
'#required' => TRUE,
|
||||
'#size' => 80,
|
||||
'#title' => $this->t('Piwik HTTP URL'),
|
||||
'#type' => 'textfield',
|
||||
];
|
||||
$form['general']['piwik_url_https'] = [
|
||||
'#default_value' => $config->get('url_https'),
|
||||
'#description' => $this->t('The URL to your Piwik base directory with SSL certificate installed. Required if you track a SSL enabled website. Example: "https://www.example.com/piwik/".'),
|
||||
'#maxlength' => 255,
|
||||
'#size' => 80,
|
||||
'#title' => $this->t('Piwik HTTPS URL'),
|
||||
'#type' => 'textfield',
|
||||
];
|
||||
// Required for automated form save testing only.
|
||||
$form['general']['piwik_url_skiperror'] = [
|
||||
'#type' => 'hidden',
|
||||
'#default_value' => FALSE,
|
||||
];
|
||||
|
||||
// Visibility settings.
|
||||
$form['tracking_scope'] = [
|
||||
'#type' => 'vertical_tabs',
|
||||
'#title' => $this->t('Tracking scope'),
|
||||
'#attached' => [
|
||||
'library' => [
|
||||
'piwik/piwik.admin',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$form['tracking']['domain_tracking'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Domains'),
|
||||
'#group' => 'tracking_scope',
|
||||
];
|
||||
|
||||
global $cookie_domain;
|
||||
$multiple_sub_domains = [];
|
||||
foreach (['www', 'app', 'shop'] as $subdomain) {
|
||||
if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) {
|
||||
$multiple_sub_domains[] = $subdomain . $cookie_domain;
|
||||
}
|
||||
// IP addresses or localhost.
|
||||
else {
|
||||
$multiple_sub_domains[] = $subdomain . '.example.com';
|
||||
}
|
||||
}
|
||||
|
||||
$form['tracking']['domain_tracking']['piwik_domain_mode'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('What are you tracking?'),
|
||||
'#options' => [
|
||||
0 => $this->t('A single domain (default)'),
|
||||
1 => $this->t('One domain with multiple subdomains'),
|
||||
],
|
||||
0 => [
|
||||
'#description' => $this->t('Domain: @domain', ['@domain' => $_SERVER['HTTP_HOST']]),
|
||||
],
|
||||
1 => [
|
||||
'#description' => $this->t('Examples: @domains', ['@domains' => implode(', ', $multiple_sub_domains)]),
|
||||
],
|
||||
'#default_value' => $config->get('domain_mode'),
|
||||
];
|
||||
|
||||
// Page specific visibility configurations.
|
||||
$account = \Drupal::currentUser();
|
||||
$php_access = $account->hasPermission('use PHP for piwik tracking visibility');
|
||||
$visibility_request_path_pages = $config->get('visibility.request_path_pages');
|
||||
|
||||
$form['tracking']['page_visibility_settings'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Pages'),
|
||||
'#group' => 'tracking_scope',
|
||||
];
|
||||
|
||||
if ($config->get('visibility.request_path_mode') == 2 && !$php_access) {
|
||||
// No permission to change PHP snippets, but keep existing settings.
|
||||
$form['tracking']['page_visibility_settings'] = [];
|
||||
$form['tracking']['page_visibility_settings']['piwik_visibility_request_path_mode'] = ['#type' => 'value', '#value' => 2];
|
||||
$form['tracking']['page_visibility_settings']['piwik_visibility_request_path_pages'] = ['#type' => 'value', '#value' => $visibility_request_path_pages];
|
||||
}
|
||||
else {
|
||||
$options = [
|
||||
t('Every page except the listed pages'),
|
||||
t('The listed pages only'),
|
||||
];
|
||||
$description = t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", ['%blog' => '/blog', '%blog-wildcard' => '/blog/*', '%front' => '<front>']);
|
||||
|
||||
if (\Drupal::moduleHandler()->moduleExists('php') && $php_access) {
|
||||
$options[] = t('Pages on which this PHP code returns <code>TRUE</code> (experts only)');
|
||||
$title = t('Pages or PHP code');
|
||||
$description .= ' ' . t('If the PHP option is chosen, enter PHP code between %php. Note that executing incorrect PHP code can break your Drupal site.', ['%php' => '<?php ?>']);
|
||||
}
|
||||
else {
|
||||
$title = t('Pages');
|
||||
}
|
||||
$form['tracking']['page_visibility_settings']['piwik_visibility_request_path_mode'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Add tracking to specific pages'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $config->get('visibility.request_path_mode'),
|
||||
];
|
||||
$form['tracking']['page_visibility_settings']['piwik_visibility_request_path_pages'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $title,
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => !empty($visibility_request_path_pages) ? $visibility_request_path_pages : '',
|
||||
'#description' => $description,
|
||||
'#rows' => 10,
|
||||
];
|
||||
}
|
||||
|
||||
// Render the role overview.
|
||||
$visibility_user_role_roles = $config->get('visibility.user_role_roles');
|
||||
|
||||
$form['tracking']['role_visibility_settings'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Roles'),
|
||||
'#group' => 'tracking_scope',
|
||||
];
|
||||
|
||||
$form['tracking']['role_visibility_settings']['piwik_visibility_user_role_mode'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Add tracking for specific roles'),
|
||||
'#options' => [
|
||||
t('Add to the selected roles only'),
|
||||
t('Add to every role except the selected ones'),
|
||||
],
|
||||
'#default_value' => $config->get('visibility.user_role_mode'),
|
||||
];
|
||||
$form['tracking']['role_visibility_settings']['piwik_visibility_user_role_roles'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Roles'),
|
||||
'#default_value' => !empty($visibility_user_role_roles) ? $visibility_user_role_roles : [],
|
||||
'#options' => array_map('\Drupal\Component\Utility\Html::escape', user_role_names()),
|
||||
'#description' => $this->t('If none of the roles are selected, all users will be tracked. If a user has any of the roles checked, that user will be tracked (or excluded, depending on the setting above).'),
|
||||
];
|
||||
|
||||
// Standard tracking configurations.
|
||||
$visibility_user_account_mode = $config->get('visibility.user_account_mode');
|
||||
|
||||
$form['tracking']['user_visibility_settings'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Users'),
|
||||
'#group' => 'tracking_scope',
|
||||
];
|
||||
$t_permission = ['%permission' => $this->t('opt-in or out of tracking')];
|
||||
$form['tracking']['user_visibility_settings']['piwik_visibility_user_account_mode'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Allow users to customize tracking on their account page'),
|
||||
'#options' => [
|
||||
t('No customization allowed'),
|
||||
t('Tracking on by default, users with %permission permission can opt out', $t_permission),
|
||||
t('Tracking off by default, users with %permission permission can opt in', $t_permission),
|
||||
],
|
||||
'#default_value' => !empty($visibility_user_account_mode) ? $visibility_user_account_mode : 0,
|
||||
];
|
||||
$form['tracking']['user_visibility_settings']['piwik_trackuserid'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Track User ID'),
|
||||
'#default_value' => $config->get('track.userid'),
|
||||
'#description' => $this->t('User ID enables the analysis of groups of sessions, across devices, using a unique, persistent, and non-personally identifiable ID string representing a user. <a href=":url">Learn more about the benefits of using User ID</a>.', [':url' => 'http://piwik.org/docs/user-id/']),
|
||||
];
|
||||
|
||||
// Link specific configurations.
|
||||
$form['tracking']['linktracking'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Links and downloads'),
|
||||
'#group' => 'tracking_scope',
|
||||
];
|
||||
$form['tracking']['linktracking']['piwik_trackmailto'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Track clicks on mailto links'),
|
||||
'#default_value' => $config->get('track.mailto'),
|
||||
];
|
||||
$form['tracking']['linktracking']['piwik_trackfiles'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Track clicks on outbound links and downloads (clicks on file links) for the following extensions'),
|
||||
'#default_value' => $config->get('track.files'),
|
||||
];
|
||||
$form['tracking']['linktracking']['piwik_trackfiles_extensions'] = [
|
||||
'#title' => $this->t('List of download file extensions'),
|
||||
'#title_display' => 'invisible',
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $config->get('track.files_extensions'),
|
||||
'#description' => $this->t('A file extension list separated by the | character that will be tracked as download when clicked. Regular expressions are supported. For example: @extensions', ['@extensions' => PIWIK_TRACKFILES_EXTENSIONS]),
|
||||
'#maxlength' => 500,
|
||||
'#states' => [
|
||||
'enabled' => [
|
||||
':input[name="piwik_trackfiles"]' => ['checked' => TRUE],
|
||||
],
|
||||
// Note: Form required marker is not visible as title is invisible.
|
||||
'required' => [
|
||||
':input[name="piwik_trackfiles"]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$colorbox_dependencies = '<div class="admin-requirements">';
|
||||
$colorbox_dependencies .= t('Requires: @module-list', ['@module-list' => (\Drupal::moduleHandler()->moduleExists('colorbox') ? t('@module (<span class="admin-enabled">enabled</span>)', ['@module' => 'Colorbox']) : t('@module (<span class="admin-missing">disabled</span>)', ['@module' => 'Colorbox']))]);
|
||||
$colorbox_dependencies .= '</div>';
|
||||
|
||||
$form['tracking']['linktracking']['piwik_trackcolorbox'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Track content in colorbox modal dialogs'),
|
||||
'#description' => t('Enable to track the content shown in colorbox modal windows.') . $colorbox_dependencies,
|
||||
'#default_value' => $config->get('track.colorbox'),
|
||||
'#disabled' => (\Drupal::moduleHandler()->moduleExists('colorbox') ? FALSE : TRUE),
|
||||
];
|
||||
|
||||
// Message specific configurations.
|
||||
$form['tracking']['messagetracking'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Messages'),
|
||||
'#group' => 'tracking_scope',
|
||||
];
|
||||
$track_messages = $config->get('track.messages');
|
||||
$form['tracking']['messagetracking']['piwik_trackmessages'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Track messages of type'),
|
||||
'#default_value' => !empty($track_messages) ? $track_messages : [],
|
||||
'#description' => $this->t('This will track the selected message types shown to users. Tracking of form validation errors may help you identifying usability issues in your site. Every message is tracked as one individual event. Messages from excluded pages cannot be tracked.'),
|
||||
'#options' => [
|
||||
'status' => $this->t('Status message'),
|
||||
'warning' => $this->t('Warning message'),
|
||||
'error' => $this->t('Error message'),
|
||||
],
|
||||
];
|
||||
|
||||
$form['tracking']['search'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Search'),
|
||||
'#group' => 'tracking_scope',
|
||||
];
|
||||
|
||||
$site_search_dependencies = '<div class="admin-requirements">';
|
||||
$site_search_dependencies .= t('Requires: @module-list', ['@module-list' => (\Drupal::moduleHandler()->moduleExists('search') ? t('@module (<span class="admin-enabled">enabled</span>)', ['@module' => 'Search']) : t('@module (<span class="admin-missing">disabled</span>)', ['@module' => 'Search']))]);
|
||||
$site_search_dependencies .= '</div>';
|
||||
|
||||
$form['tracking']['search']['piwik_site_search'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Track internal search'),
|
||||
'#description' => $this->t('If checked, internal search keywords are tracked.') . $site_search_dependencies,
|
||||
'#default_value' => $config->get('track.site_search'),
|
||||
'#disabled' => (\Drupal::moduleHandler()->moduleExists('search') ? FALSE : TRUE),
|
||||
];
|
||||
|
||||
// Privacy specific configurations.
|
||||
$form['tracking']['privacy'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Privacy'),
|
||||
'#group' => 'tracking_scope',
|
||||
];
|
||||
$form['tracking']['privacy']['piwik_privacy_donottrack'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Universal web tracking opt-out'),
|
||||
'#description' => $this->t('If enabled and your Piwik server receives the <a href="http://donottrack.us/">Do-Not-Track</a> header from the client browser, the Piwik server will not track the user. Compliance with Do Not Track could be purely voluntary, enforced by industry self-regulation, or mandated by state or federal law. Please accept your visitors privacy. If they have opt-out from tracking and advertising, you should accept their personal decision.'),
|
||||
'#default_value' => $config->get('privacy.donottrack'),
|
||||
];
|
||||
|
||||
// Piwik page title tree view settings.
|
||||
$form['page_title_hierarchy'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Page titles hierarchy'),
|
||||
'#description' => $this->t('This functionality enables a dynamically expandable tree view of your site page titles in your Piwik statistics. See in Piwik statistics under <em>Actions</em> > <em>Page titles</em>.'),
|
||||
'#group' => 'page_title_hierarchy',
|
||||
];
|
||||
$form['page_title_hierarchy']['piwik_page_title_hierarchy'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t("Show page titles as hierarchy like breadcrumbs"),
|
||||
'#description' => $this->t('By default Piwik tracks the current page title and shows you a flat list of the most popular titles. This enables a breadcrumbs like tree view.'),
|
||||
'#default_value' => $config->get('page_title_hierarchy'),
|
||||
];
|
||||
$form['page_title_hierarchy']['piwik_page_title_hierarchy_exclude_home'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Hide home page from hierarchy'),
|
||||
'#description' => $this->t('If enabled, the "Home" item will be removed from the hierarchy to flatten the structure in the Piwik statistics. Hits to the home page will still be counted, but for other pages the hierarchy will start at level Home+1.'),
|
||||
'#default_value' => $config->get('page_title_hierarchy_exclude_home'),
|
||||
];
|
||||
|
||||
// Custom variables.
|
||||
$form['piwik_custom_var'] = [
|
||||
'#description' => $this->t('You can add Piwiks <a href=":custom_var_documentation">Custom Variables</a> here. These will be added to every page that Piwik tracking code appears on. Custom variable names and values are limited to 200 characters in length. Keep the names and values as short as possible and expect long values to get trimmed. You may use tokens in custom variable names and values. Global and user tokens are always available; on node pages, node tokens are also available.', [':custom_var_documentation' => 'http://piwik.org/docs/custom-variables/']),
|
||||
'#title' => $this->t('Custom variables'),
|
||||
'#tree' => TRUE,
|
||||
'#type' => 'details',
|
||||
];
|
||||
|
||||
$form['piwik_custom_var']['slots'] = [
|
||||
'#type' => 'table',
|
||||
'#header' => [
|
||||
['data' => $this->t('Slot')],
|
||||
['data' => $this->t('Name')],
|
||||
['data' => $this->t('Value')],
|
||||
['data' => $this->t('Scope')],
|
||||
],
|
||||
];
|
||||
|
||||
$piwik_custom_vars = $config->get('custom.variable');
|
||||
|
||||
// Piwik supports up to 5 custom variables.
|
||||
for ($i = 1; $i < 6; $i++) {
|
||||
$form['piwik_custom_var']['slots'][$i]['slot'] = [
|
||||
'#default_value' => $i,
|
||||
'#description' => $this->t('Slot number'),
|
||||
'#disabled' => TRUE,
|
||||
'#size' => 1,
|
||||
'#title' => $this->t('Custom variable slot #@slot', ['@slot' => $i]),
|
||||
'#title_display' => 'invisible',
|
||||
'#type' => 'textfield',
|
||||
];
|
||||
$form['piwik_custom_var']['slots'][$i]['name'] = [
|
||||
'#default_value' => isset($piwik_custom_vars[$i]['name']) ? $piwik_custom_vars[$i]['name'] : '',
|
||||
'#description' => $this->t('The custom variable name.'),
|
||||
'#maxlength' => 100,
|
||||
'#size' => 20,
|
||||
'#title' => $this->t('Custom variable name #@slot', ['@slot' => $i]),
|
||||
'#title_display' => 'invisible',
|
||||
'#type' => 'textfield',
|
||||
];
|
||||
$form['piwik_custom_var']['slots'][$i]['value'] = [
|
||||
'#default_value' => isset($piwik_custom_vars[$i]['value']) ? $piwik_custom_vars[$i]['value'] : '',
|
||||
'#description' => $this->t('The custom variable value.'),
|
||||
'#maxlength' => 255,
|
||||
'#title' => $this->t('Custom variable value #@slot', ['@slot' => $i]),
|
||||
'#title_display' => 'invisible',
|
||||
'#type' => 'textfield',
|
||||
'#element_validate' => [[get_class($this), 'tokenElementValidate']],
|
||||
'#token_types' => ['node'],
|
||||
];
|
||||
if (\Drupal::moduleHandler()->moduleExists('token')) {
|
||||
$form['piwik_custom_var']['slots'][$i]['value']['#element_validate'][] = 'token_element_validate';
|
||||
}
|
||||
$form['piwik_custom_var']['slots'][$i]['scope'] = [
|
||||
'#default_value' => isset($piwik_custom_vars[$i]['scope']) ? $piwik_custom_vars[$i]['scope'] : '',
|
||||
'#description' => $this->t('The scope for the custom variable.'),
|
||||
'#title' => $this->t('Custom variable slot #@slot', ['@slot' => $i]),
|
||||
'#title_display' => 'invisible',
|
||||
'#type' => 'select',
|
||||
'#options' => [
|
||||
'visit' => $this->t('Visit'),
|
||||
'page' => $this->t('Page'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$form['piwik_custom_var']['piwik_custom_var_description'] = [
|
||||
'#type' => 'item',
|
||||
'#description' => $this->t("You can supplement Piwiks' basic IP address tracking of visitors by segmenting users based on custom variables. Make sure you will not associate (or permit any third party to associate) any data gathered from your websites (or such third parties' websites) with any personally identifying information from any source as part of your use (or such third parties' use) of the Piwik' service."),
|
||||
];
|
||||
if (\Drupal::moduleHandler()->moduleExists('token')) {
|
||||
$form['piwik_custom_var']['piwik_custom_var_token_tree'] = [
|
||||
'#theme' => 'token_tree_link',
|
||||
'#token_types' => ['node'],
|
||||
];
|
||||
}
|
||||
|
||||
// Advanced feature configurations.
|
||||
$form['advanced'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Advanced settings'),
|
||||
'#open' => FALSE,
|
||||
];
|
||||
|
||||
$form['advanced']['piwik_cache'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Locally cache tracking code file'),
|
||||
'#description' => $this->t('If checked, the tracking code file is retrieved from your Piwik site and cached locally. It is updated daily to ensure updates to tracking code are reflected in the local copy.'),
|
||||
'#default_value' => $config->get('cache'),
|
||||
];
|
||||
|
||||
// Allow for tracking of the originating node when viewing translation sets.
|
||||
if (\Drupal::moduleHandler()->moduleExists('content_translation')) {
|
||||
$form['advanced']['piwik_translation_set'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Track translation sets as one unit'),
|
||||
'#description' => $this->t('When a node is part of a translation set, record statistics for the originating node instead. This allows for a translation set to be treated as a single unit.'),
|
||||
'#default_value' => $config->get('translation_set'),
|
||||
];
|
||||
}
|
||||
|
||||
$user_access_add_js_snippets = !$this->currentUser()->hasPermission('add JS snippets for piwik');
|
||||
$user_access_add_js_snippets_permission_warning = $user_access_add_js_snippets ? ' <em>' . $this->t('This field has been disabled because you do not have sufficient permissions to edit it.') . '</em>' : '';
|
||||
$form['advanced']['codesnippet'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Custom JavaScript code'),
|
||||
'#open' => TRUE,
|
||||
'#description' => $this->t('You can add custom Piwik <a href=":snippets">code snippets</a> here. These will be added to every page that Piwik appears on. <strong>Do not include the <script> tags</strong>, and always end your code with a semicolon (;).', [':snippets' => 'http://piwik.org/docs/javascript-tracking/']),
|
||||
];
|
||||
$form['advanced']['codesnippet']['piwik_codesnippet_before'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Code snippet (before)'),
|
||||
'#default_value' => $config->get('codesnippet.before'),
|
||||
'#disabled' => $user_access_add_js_snippets,
|
||||
'#rows' => 5,
|
||||
'#description' => $this->t('Code in this textarea will be added <strong>before</strong> _paq.push(["trackPageView"]).') . $user_access_add_js_snippets_permission_warning,
|
||||
];
|
||||
$form['advanced']['codesnippet']['piwik_codesnippet_after'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Code snippet (after)'),
|
||||
'#default_value' => $config->get('codesnippet.after'),
|
||||
'#disabled' => $user_access_add_js_snippets,
|
||||
'#rows' => 5,
|
||||
'#description' => $this->t('Code in this textarea will be added <strong>after</strong> _paq.push(["trackPageView"]). This is useful if you\'d like to track a site in two accounts.') . $user_access_add_js_snippets_permission_warning,
|
||||
];
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::validateForm($form, $form_state);
|
||||
|
||||
// Custom variables validation.
|
||||
foreach ($form_state->getValue(['piwik_custom_var', 'slots']) as $custom_var) {
|
||||
$form_state->setValue(['piwik_custom_var', 'slots', $custom_var['slot'], 'name'], trim($custom_var['name']));
|
||||
$form_state->setValue(['piwik_custom_var', 'slots', $custom_var['slot'], 'value'], trim($custom_var['value']));
|
||||
|
||||
// Validate empty names/values.
|
||||
if (empty($custom_var['name']) && !empty($custom_var['value'])) {
|
||||
$form_state->setErrorByName("piwik_custom_var][slots][" . $custom_var['slot'] . "][name", t('The custom variable @slot-number requires a <em>Name</em> if a <em>Value</em> has been provided.', ['@slot-number' => $custom_var['slot']]));
|
||||
}
|
||||
elseif (!empty($custom_var['name']) && empty($custom_var['value'])) {
|
||||
$form_state->setErrorByName("piwik_custom_var][slots][" . $custom_var['slot'] . "][value", t('The custom variable @slot-number requires a <em>Value</em> if a <em>Name</em> has been provided.', ['@slot-number' => $custom_var['slot']]));
|
||||
}
|
||||
}
|
||||
$form_state->setValue('piwik_custom_var', $form_state->getValue(['piwik_custom_var', 'slots']));
|
||||
|
||||
// Trim some text area values.
|
||||
$form_state->setValue('piwik_site_id', trim($form_state->getValue('piwik_site_id')));
|
||||
$form_state->setValue('piwik_visibility_request_path_pages', trim($form_state->getValue('piwik_visibility_request_path_pages')));
|
||||
$form_state->setValue('piwik_codesnippet_before', trim($form_state->getValue('piwik_codesnippet_before')));
|
||||
$form_state->setValue('piwik_codesnippet_after', trim($form_state->getValue('piwik_codesnippet_after')));
|
||||
$form_state->setValue('piwik_visibility_user_role_roles', array_filter($form_state->getValue('piwik_visibility_user_role_roles')));
|
||||
$form_state->setValue('piwik_trackmessages', array_filter($form_state->getValue('piwik_trackmessages')));
|
||||
|
||||
if (!preg_match('/^\d{1,}$/', $form_state->getValue('piwik_site_id'))) {
|
||||
$form_state->setErrorByName('piwik_site_id', t('A valid Piwik site ID is an integer only.'));
|
||||
}
|
||||
|
||||
$url = $form_state->getValue('piwik_url_http') . 'piwik.php';
|
||||
try {
|
||||
$result = \Drupal::httpClient()->get($url);
|
||||
if ($result->getStatusCode() != 200 && $form_state->getValue('piwik_url_skiperror') == FALSE) {
|
||||
$form_state->setErrorByName('piwik_url_http', t('The validation of "@url" failed with error "@error" (HTTP code @code).', [
|
||||
'@url' => UrlHelper::filterBadProtocol($url),
|
||||
'@error' => $result->getReasonPhrase(),
|
||||
'@code' => $result->getStatusCode(),
|
||||
]));
|
||||
}
|
||||
}
|
||||
catch (RequestException $exception) {
|
||||
$form_state->setErrorByName('piwik_url_http', t('The validation of "@url" failed with an exception "@error" (HTTP code @code).', [
|
||||
'@url' => UrlHelper::filterBadProtocol($url),
|
||||
'@error' => $exception->getMessage(),
|
||||
'@code' => $exception->getCode(),
|
||||
]));
|
||||
}
|
||||
|
||||
$piwik_url_https = $form_state->getValue('piwik_url_https');
|
||||
if (!empty($piwik_url_https)) {
|
||||
$url = $piwik_url_https . 'piwik.php';
|
||||
try {
|
||||
$result = \Drupal::httpClient()->get($url);
|
||||
if ($result->getStatusCode() != 200 && $form_state->getValue('piwik_url_skiperror') == FALSE) {
|
||||
$form_state->setErrorByName('piwik_url_https', t('The validation of "@url" failed with error "@error" (HTTP code @code).', [
|
||||
'@url' => UrlHelper::filterBadProtocol($url),
|
||||
'@error' => $result->getReasonPhrase(),
|
||||
'@code' => $result->getStatusCode(),
|
||||
]));
|
||||
}
|
||||
}
|
||||
catch (RequestException $exception) {
|
||||
$form_state->setErrorByName('piwik_url_https', t('The validation of "@url" failed with an exception "@error" (HTTP code @code).', [
|
||||
'@url' => UrlHelper::filterBadProtocol($url),
|
||||
'@error' => $exception->getMessage(),
|
||||
'@code' => $exception->getCode(),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that every path is prefixed with a slash, but don't check PHP
|
||||
// code snippets.
|
||||
if ($form_state->getValue('piwik_visibility_request_path_mode') != 2) {
|
||||
$pages = preg_split('/(\r\n?|\n)/', $form_state->getValue('piwik_visibility_request_path_pages'));
|
||||
foreach ($pages as $page) {
|
||||
if (strpos($page, '/') !== 0 && $page !== '<front>') {
|
||||
$form_state->setErrorByName('piwik_visibility_request_path_pages', t('Path "@page" not prefixed with slash.', ['@page' => $page]));
|
||||
// Drupal forms show one error only.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear obsolete local cache if cache has been disabled.
|
||||
if ($form_state->isValueEmpty('piwik_cache') && $form['advanced']['piwik_cache']['#default_value']) {
|
||||
piwik_clear_js_cache();
|
||||
}
|
||||
|
||||
// This is for the Newbie's who cannot read a text area description.
|
||||
if (preg_match('/(.*)<\/?script(.*)>(.*)/i', $form_state->getValue('piwik_codesnippet_before'))) {
|
||||
$form_state->setErrorByName('piwik_codesnippet_before', t('Do not include the <script> tags in the javascript code snippets.'));
|
||||
}
|
||||
if (preg_match('/(.*)<\/?script(.*)>(.*)/i', $form_state->getValue('piwik_codesnippet_after'))) {
|
||||
$form_state->setErrorByName('piwik_codesnippet_after', t('Do not include the <script> tags in the javascript code snippets.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$config = $this->config('piwik.settings');
|
||||
$config
|
||||
->set('site_id', $form_state->getValue('piwik_site_id'))
|
||||
->set('url_http', $form_state->getValue('piwik_url_http'))
|
||||
->set('url_https', $form_state->getValue('piwik_url_https'))
|
||||
->set('codesnippet.before', $form_state->getValue('piwik_codesnippet_before'))
|
||||
->set('codesnippet.after', $form_state->getValue('piwik_codesnippet_after'))
|
||||
->set('custom.variable', $form_state->getValue('piwik_custom_var'))
|
||||
->set('domain_mode', $form_state->getValue('piwik_domain_mode'))
|
||||
->set('track.files', $form_state->getValue('piwik_trackfiles'))
|
||||
->set('track.files_extensions', $form_state->getValue('piwik_trackfiles_extensions'))
|
||||
->set('track.colorbox', $form_state->getValue('piwik_trackcolorbox'))
|
||||
->set('track.userid', $form_state->getValue('piwik_trackuserid'))
|
||||
->set('track.mailto', $form_state->getValue('piwik_trackmailto'))
|
||||
->set('track.messages', $form_state->getValue('piwik_trackmessages'))
|
||||
->set('track.site_search', $form_state->getValue('piwik_site_search'))
|
||||
->set('privacy.donottrack', $form_state->getValue('piwik_privacy_donottrack'))
|
||||
->set('cache', $form_state->getValue('piwik_cache'))
|
||||
->set('visibility.request_path_mode', $form_state->getValue('piwik_visibility_request_path_mode'))
|
||||
->set('visibility.request_path_pages', $form_state->getValue('piwik_visibility_request_path_pages'))
|
||||
->set('visibility.user_account_mode', $form_state->getValue('piwik_visibility_user_account_mode'))
|
||||
->set('visibility.user_role_mode', $form_state->getValue('piwik_visibility_user_role_mode'))
|
||||
->set('visibility.user_role_roles', $form_state->getValue('piwik_visibility_user_role_roles'))
|
||||
->save();
|
||||
|
||||
if ($form_state->hasValue('piwik_translation_set')) {
|
||||
$config->set('translation_set', $form_state->getValue('piwik_translation_set'))->save();
|
||||
}
|
||||
|
||||
parent::submitForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a form element that should have tokens in it.
|
||||
*
|
||||
* For example:
|
||||
* @code
|
||||
* $form['my_node_text_element'] = [
|
||||
* '#type' => 'textfield',
|
||||
* '#title' => $this->t('Some text to token-ize that has a node context.'),
|
||||
* '#default_value' => 'The title of this node is [node:title].',
|
||||
* '#element_validate' => [[get_class($this), 'tokenElementValidate']],
|
||||
* ];
|
||||
* @endcode
|
||||
*/
|
||||
public static function tokenElementValidate(&$element, FormStateInterface $form_state) {
|
||||
$value = isset($element['#value']) ? $element['#value'] : $element['#default_value'];
|
||||
|
||||
if (!Unicode::strlen($value)) {
|
||||
// Empty value needs no further validation since the element should depend
|
||||
// on using the '#required' FAPI property.
|
||||
return $element;
|
||||
}
|
||||
|
||||
$tokens = \Drupal::token()->scan($value);
|
||||
$invalid_tokens = static::getForbiddenTokens($tokens);
|
||||
if ($invalid_tokens) {
|
||||
$form_state->setError($element, t('The %element-title is using the following forbidden tokens with personal identifying information: @invalid-tokens.', ['%element-title' => $element['#title'], '@invalid-tokens' => implode(', ', $invalid_tokens)]));
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all forbidden tokens.
|
||||
*
|
||||
* @param array $value
|
||||
* An array of token values.
|
||||
*
|
||||
* @return array
|
||||
* A unique array of invalid tokens.
|
||||
*/
|
||||
protected static function getForbiddenTokens(array $value) {
|
||||
$invalid_tokens = [];
|
||||
$value_tokens = is_string($value) ? \Drupal::token()->scan($value) : $value;
|
||||
|
||||
foreach ($value_tokens as $tokens) {
|
||||
if (array_filter($tokens, 'static::containsForbiddenToken')) {
|
||||
$invalid_tokens = array_merge($invalid_tokens, array_values($tokens));
|
||||
}
|
||||
}
|
||||
|
||||
array_unique($invalid_tokens);
|
||||
return $invalid_tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if string contains forbidden tokens not allowed by privacy rules.
|
||||
*
|
||||
* @param string $token_string
|
||||
* A string with one or more tokens to be validated.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if blacklisted token has been found, otherwise FALSE.
|
||||
*/
|
||||
protected static function containsForbiddenToken($token_string) {
|
||||
// List of strings in tokens with personal identifying information not
|
||||
// allowed for privacy reasons. See section 8.1 of the Google Analytics
|
||||
// terms of use for more detailed information.
|
||||
//
|
||||
// This list can never ever be complete. For this reason it tries to use a
|
||||
// regex and may kill a few other valid tokens, but it's the only way to
|
||||
// protect users as much as possible from admins with illegal ideas.
|
||||
//
|
||||
// User tokens are not prefixed with colon to catch 'current-user' and
|
||||
// 'user'.
|
||||
//
|
||||
// TODO: If someone have better ideas, share them, please!
|
||||
$token_blacklist = [
|
||||
':account-name]',
|
||||
':author]',
|
||||
':author:edit-url]',
|
||||
':author:url]',
|
||||
':author:path]',
|
||||
':current-user]',
|
||||
':current-user:original]',
|
||||
':display-name]',
|
||||
':fid]',
|
||||
':mail]',
|
||||
':name]',
|
||||
':uid]',
|
||||
':one-time-login-url]',
|
||||
':owner]',
|
||||
':owner:cancel-url]',
|
||||
':owner:edit-url]',
|
||||
':owner:url]',
|
||||
':owner:path]',
|
||||
'user:cancel-url]',
|
||||
'user:edit-url]',
|
||||
'user:url]',
|
||||
'user:path]',
|
||||
'user:picture]',
|
||||
// addressfield_tokens.module
|
||||
':first-name]',
|
||||
':last-name]',
|
||||
':name-line]',
|
||||
':mc-address]',
|
||||
':thoroughfare]',
|
||||
':premise]',
|
||||
// realname.module
|
||||
':name-raw]',
|
||||
// token.module
|
||||
':ip-address]',
|
||||
];
|
||||
|
||||
return preg_match('/' . implode('|', array_map('preg_quote', $token_blacklist)) . '/i', $token_string);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* This plugin flattens the custom variables array.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "piwik_custom_vars"
|
||||
* )
|
||||
*/
|
||||
class PiwikCustomVars extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* Flatten custom vars array.
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
list($piwik_custom_vars) = $value;
|
||||
|
||||
return isset($piwik_custom_vars['slots']) ? $piwik_custom_vars['slots'] : [];
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
|
||||
/**
|
||||
* If the source evaluates to empty, we skip the current row.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "piwik_skip_row_if_not_set",
|
||||
* handle_multiples = TRUE
|
||||
* )
|
||||
*/
|
||||
class PiwikSkipRowIfNotSet extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!isset($value[$this->configuration['module']][$this->configuration['key']])) {
|
||||
throw new MigrateSkipRowException();
|
||||
}
|
||||
return $value[$this->configuration['module']][$this->configuration['key']];
|
||||
}
|
||||
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
use Drupal\migrate\Plugin\MigrateProcessInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Prefixes paths with a slash.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "piwik_visibility_pages"
|
||||
* )
|
||||
*/
|
||||
class PiwikVisibilityPages extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The migration process plugin, configured for lookups in the d6_user_role
|
||||
* and d7_user_role migrations.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*/
|
||||
protected $migrationPlugin;
|
||||
|
||||
/**
|
||||
* Whether or not to skip Piwik that use PHP for visibility. Only applies if
|
||||
* the PHP module is not enabled.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $skipPHP = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ModuleHandlerInterface $module_handler, MigrateProcessInterface $migration_plugin) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->migrationPlugin = $migration_plugin;
|
||||
|
||||
if (isset($configuration['skip_php'])) {
|
||||
$this->skipPHP = $configuration['skip_php'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
$migration_configuration = [
|
||||
'migration' => [
|
||||
'd6_user_role',
|
||||
'd7_user_role',
|
||||
],
|
||||
];
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('module_handler'),
|
||||
$container->get('plugin.manager.migrate.process')->createInstance('migration', $migration_configuration, $migration)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
list($old_visibility, $pages) = $value;
|
||||
|
||||
$request_path_pages = '';
|
||||
|
||||
if ($pages) {
|
||||
// 2 == BLOCK_VISIBILITY_PHP in Drupal 6 and 7.
|
||||
if ($old_visibility == 2) {
|
||||
// If the PHP module is present, migrate the visibility code unaltered.
|
||||
if ($this->moduleHandler->moduleExists('php')) {
|
||||
$request_path_pages = $pages;
|
||||
}
|
||||
// Skip the row if we're configured to. If not, we don't need to do
|
||||
// anything else -- the block will simply have no PHP or request_path
|
||||
// visibility configuration.
|
||||
elseif ($this->skipPHP) {
|
||||
throw new MigrateSkipRowException();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$paths = preg_split("(\r\n?|\n)", $pages);
|
||||
foreach ($paths as $key => $path) {
|
||||
$paths[$key] = $path === '<front>' ? $path : '/' . ltrim($path, '/');
|
||||
}
|
||||
$request_path_pages = implode("\n", $paths);
|
||||
}
|
||||
}
|
||||
|
||||
return $request_path_pages;
|
||||
}
|
||||
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Plugin\MigrateProcessInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Converts D7 role ids to D8 role names.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "piwik_visibility_roles"
|
||||
* )
|
||||
*/
|
||||
class PiwikVisibilityRoles extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The migration process plugin, configured for lookups in the d6_user_role
|
||||
* and d7_user_role migrations.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*/
|
||||
protected $migrationPlugin;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ModuleHandlerInterface $module_handler, MigrateProcessInterface $migration_plugin) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->migrationPlugin = $migration_plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
$migration_configuration = [
|
||||
'migration' => [
|
||||
'd6_user_role',
|
||||
'd7_user_role',
|
||||
],
|
||||
];
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('module_handler'),
|
||||
$container->get('plugin.manager.migrate.process')->createInstance('migration', $migration_configuration, $migration)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
list($roles) = $value;
|
||||
|
||||
// Remove role IDs disabled in D6/D7.
|
||||
$roles = array_filter($roles);
|
||||
|
||||
$user_role_roles = [];
|
||||
|
||||
if ($roles) {
|
||||
foreach ($roles as $key => $role_id) {
|
||||
$roles[$key] = $this->migrationPlugin->transform($role_id, $migrate_executable, $row, $destination_property);
|
||||
}
|
||||
$user_role_roles = array_combine($roles, $roles);
|
||||
}
|
||||
|
||||
return $user_role_roles;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Tests;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Test basic functionality of Piwik module.
|
||||
*
|
||||
* @group Piwik
|
||||
*/
|
||||
class PiwikBasicTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* User without permissions to use snippets.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $noSnippetUser;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['piwik'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer piwik',
|
||||
];
|
||||
|
||||
// User to set up piwik.
|
||||
$this->noSnippetUser = $this->drupalCreateUser($permissions);
|
||||
$permissions[] = 'add JS snippets for piwik';
|
||||
$this->admin_user = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($this->admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if configuration is possible.
|
||||
*/
|
||||
public function testPiwikConfiguration() {
|
||||
// Check for setting page's presence.
|
||||
$this->drupalGet('admin/config/system/piwik');
|
||||
$this->assertRaw(t('Piwik site ID'), '[testPiwikConfiguration]: Settings page displayed.');
|
||||
|
||||
// Check for account code validation.
|
||||
$edit['piwik_site_id'] = $this->randomMachineName(2);
|
||||
$edit['piwik_url_http'] = 'http://www.example.com/piwik/';
|
||||
$this->drupalPostForm('admin/config/system/piwik', $edit, 'Save configuration');
|
||||
$this->assertRaw(t('A valid Piwik site ID is an integer only.'), '[testPiwikConfiguration]: Invalid Piwik site ID number validated.');
|
||||
|
||||
// Verify that invalid URLs throw a form error.
|
||||
$edit = [];
|
||||
$edit['piwik_site_id'] = 1;
|
||||
$edit['piwik_url_http'] = 'http://www.example.com/piwik/';
|
||||
$edit['piwik_url_https'] = 'https://www.example.com/piwik/';
|
||||
$this->drupalPostForm('admin/config/system/piwik', $edit, t('Save configuration'));
|
||||
$this->assertRaw('The validation of "http://www.example.com/piwik/piwik.php" failed with an exception', '[testPiwikConfiguration]: HTTP URL exception shown.');
|
||||
$this->assertRaw('The validation of "https://www.example.com/piwik/piwik.php" failed with an exception', '[testPiwikConfiguration]: HTTPS URL exception shown.');
|
||||
|
||||
// User should have access to code snippets.
|
||||
$this->assertFieldByName('piwik_codesnippet_before');
|
||||
$this->assertFieldByName('piwik_codesnippet_after');
|
||||
$this->assertNoFieldByXPath("//textarea[@name='piwik_codesnippet_before' and @disabled='disabled']", NULL, '"Code snippet (before)" is enabled.');
|
||||
$this->assertNoFieldByXPath("//textarea[@name='piwik_codesnippet_after' and @disabled='disabled']", NULL, '"Code snippet (after)" is enabled.');
|
||||
|
||||
// Login as user without JS permissions.
|
||||
$this->drupalLogin($this->noSnippetUser);
|
||||
$this->drupalGet('admin/config/system/piwik');
|
||||
|
||||
// User should *not* have access to snippets, but create fields.
|
||||
$this->assertFieldByName('piwik_codesnippet_before');
|
||||
$this->assertFieldByName('piwik_codesnippet_after');
|
||||
$this->assertFieldByXPath("//textarea[@name='piwik_codesnippet_before' and @disabled='disabled']", NULL, '"Code snippet (before)" is disabled.');
|
||||
$this->assertFieldByXPath("//textarea[@name='piwik_codesnippet_after' and @disabled='disabled']", NULL, '"Code snippet (after)" is disabled.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if page visibility works.
|
||||
*/
|
||||
public function testPiwikPageVisibility() {
|
||||
$site_id = '1';
|
||||
$this->config('piwik.settings')->set('site_id', $site_id)->save();
|
||||
$this->config('piwik.settings')->set('url_http', 'http://www.example.com/piwik/')->save();
|
||||
$this->config('piwik.settings')->set('url_https', 'https://www.example.com/piwik/')->save();
|
||||
|
||||
// Show tracking on "every page except the listed pages".
|
||||
$this->config('piwik.settings')->set('visibility.request_path_mode', 0)->save();
|
||||
// Disable tracking one "admin*" pages only.
|
||||
$this->config('piwik.settings')->set('visibility.request_path_pages', "/admin\n/admin/*")->save();
|
||||
// Enable tracking only for authenticated users only.
|
||||
$this->config('piwik.settings')->set('visibility.user_role_roles', [AccountInterface::AUTHENTICATED_ROLE => AccountInterface::AUTHENTICATED_ROLE])->save();
|
||||
|
||||
// Check tracking code visibility.
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw('u+"piwik.php"', '[testPiwikPageVisibility]: Tracking code is displayed for authenticated users.');
|
||||
|
||||
// Test whether tracking code is not included on pages to omit.
|
||||
$this->drupalGet('admin');
|
||||
$this->assertNoRaw('u+"piwik.php"', '[testPiwikPageVisibility]: Tracking code is not displayed on admin page.');
|
||||
$this->drupalGet('admin/config/system/piwik');
|
||||
// Checking for tracking URI here, as $site_id is displayed in the form.
|
||||
$this->assertNoRaw('u+"piwik.php"', '[testPiwikPageVisibility]: Tracking code is not displayed on admin subpage.');
|
||||
|
||||
// Test whether tracking code display is properly flipped.
|
||||
$this->config('piwik.settings')->set('visibility.request_path_mode', 1)->save();
|
||||
$this->drupalGet('admin');
|
||||
$this->assertRaw('u+"piwik.php"', '[testPiwikPageVisibility]: Tracking code is displayed on admin page.');
|
||||
$this->drupalGet('admin/config/system/piwik');
|
||||
// Checking for tracking URI here, as $site_id is displayed in the form.
|
||||
$this->assertRaw('u+"piwik.php"', '[testPiwikPageVisibility]: Tracking code is displayed on admin subpage.');
|
||||
$this->drupalGet('');
|
||||
$this->assertNoRaw('u+"piwik.php"', '[testPiwikPageVisibility]: Tracking code is NOT displayed on front page.');
|
||||
|
||||
// Test whether tracking code is not display for anonymous.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('');
|
||||
$this->assertNoRaw('u+"piwik.php"', '[testPiwikPageVisibility]: Tracking code is NOT displayed for anonymous.');
|
||||
|
||||
// Switch back to every page except the listed pages.
|
||||
$this->config('piwik.settings')->set('visibility.request_path_mode', 0)->save();
|
||||
// Enable tracking code for all user roles.
|
||||
$this->config('piwik.settings')->set('visibility.user_role_roles', [])->save();
|
||||
|
||||
// Test whether 403 forbidden tracking code is shown if user has no access.
|
||||
$this->drupalGet('admin');
|
||||
$this->assertRaw('"403/URL = "', '[testPiwikPageVisibility]: 403 Forbidden tracking code shown if user has no access.');
|
||||
|
||||
// Test whether 404 not found tracking code is shown on non-existent pages.
|
||||
$this->drupalGet($this->randomMachineName(64));
|
||||
$this->assertRaw('"404/URL = "', '[testPiwikPageVisibility]: 404 Not Found tracking code shown on non-existent page.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if tracking code is properly added to the page.
|
||||
*/
|
||||
public function testPiwikTrackingCode() {
|
||||
$site_id = '2';
|
||||
$this->config('piwik.settings')->set('site_id', $site_id)->save();
|
||||
$this->config('piwik.settings')->set('url_http', 'http://www.example.com/piwik/')->save();
|
||||
$this->config('piwik.settings')->set('url_https', 'https://www.example.com/piwik/')->save();
|
||||
|
||||
// Show tracking code on every page except the listed pages.
|
||||
$this->config('piwik.settings')->set('visibility.request_path_mode', 0)->save();
|
||||
// Enable tracking code for all user roles.
|
||||
$this->config('piwik.settings')->set('visibility.user_role_roles', [])->save();
|
||||
|
||||
/* Sample JS code as added to page:
|
||||
<script type="text/javascript">
|
||||
var _paq = _paq || [];
|
||||
(function(){
|
||||
var u=(("https:" == document.location.protocol) ? "https://{$PIWIK_URL}" : "http://{$PIWIK_URL}");
|
||||
_paq.push(['setSiteId', {$IDSITE}]);
|
||||
_paq.push(['setTrackerUrl', u+'piwik.php']);
|
||||
_paq.push(['trackPageView']);
|
||||
var d=document,
|
||||
g=d.createElement('script'),
|
||||
s=d.getElementsByTagName('script')[0];
|
||||
g.type='text/javascript';
|
||||
g.defer=true;
|
||||
g.async=true;
|
||||
g.src=u+'piwik.js';
|
||||
s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
</script>
|
||||
*/
|
||||
|
||||
// Test whether tracking code uses latest JS.
|
||||
$this->config('piwik.settings')->set('cache', 0)->save();
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw('u+"piwik.php"', '[testPiwikTrackingCode]: Latest tracking code used.');
|
||||
|
||||
// Test if tracking of User ID is enabled.
|
||||
$this->config('piwik.settings')->set('track.userid', 1)->save();
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw('_paq.push(["setUserId", ', '[testPiwikTrackingCode]: Tracking code for User ID is enabled.');
|
||||
|
||||
// Test if tracking of User ID is disabled.
|
||||
$this->config('piwik.settings')->set('track.userid', 0)->save();
|
||||
$this->drupalGet('');
|
||||
$this->assertNoRaw('_paq.push(["setUserId", ', '[testPiwikTrackingCode]: Tracking code for User ID is disabled.');
|
||||
|
||||
// Test whether single domain tracking is active.
|
||||
$this->drupalGet('');
|
||||
$this->assertNoRaw('_paq.push(["setCookieDomain"', '[testPiwikTrackingCode]: Single domain tracking is active.');
|
||||
|
||||
// Enable "One domain with multiple subdomains".
|
||||
$this->config('piwik.settings')->set('domain_mode', 1)->save();
|
||||
$this->drupalGet('');
|
||||
|
||||
// Test may run on localhost, an ipaddress or real domain name.
|
||||
// TODO: Workaround to run tests successfully. This feature cannot tested
|
||||
// reliable.
|
||||
global $cookie_domain;
|
||||
if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) {
|
||||
$this->assertRaw('_paq.push(["setCookieDomain"', '[testPiwikTrackingCode]: One domain with multiple subdomains is active on real host.');
|
||||
}
|
||||
else {
|
||||
// Special cases, Localhost and IP addresses don't show 'setCookieDomain'.
|
||||
$this->assertNoRaw('_paq.push(["setCookieDomain"', '[testPiwikTrackingCode]: One domain with multiple subdomains may be active on localhost (test result is not reliable).');
|
||||
}
|
||||
|
||||
// Test whether the BEFORE and AFTER code is added to the tracker.
|
||||
$this->config('piwik.settings')->set('codesnippet.before', '_paq.push(["setLinkTrackingTimer", 250]);')->save();
|
||||
$this->config('piwik.settings')->set('codesnippet.after', '_paq.push(["t2.setSiteId", 2]);if(1 == 1 && 2 < 3 && 2 > 1){console.log("Piwik: Custom condition works.");}_gaq.push(["t2.trackPageView"]);')->save();
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw('setLinkTrackingTimer', '[testPiwikTrackingCode]: Before codesnippet has been found with "setLinkTrackingTimer" set.');
|
||||
$this->assertRaw('t2.trackPageView', '[testPiwikTrackingCode]: After codesnippet with "t2" tracker has been found.');
|
||||
$this->assertRaw('if(1 == 1 && 2 < 3 && 2 > 1){console.log("Piwik: Custom condition works.");}', '[testPiwikTrackingCode]: JavaScript code is not HTML escaped.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Component\Serialization\Json;
|
||||
|
||||
/**
|
||||
* Test custom url functionality of Piwik module.
|
||||
*
|
||||
* @group Piwik
|
||||
*/
|
||||
class PiwikCustomUrls extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['piwik'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer piwik',
|
||||
'administer modules',
|
||||
'administer site configuration',
|
||||
];
|
||||
|
||||
// User to set up piwik.
|
||||
$this->admin_user = $this->drupalCreateUser($permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if user password page urls are overridden.
|
||||
*/
|
||||
public function testPiwikUserPasswordPage() {
|
||||
$base_path = base_path();
|
||||
$site_id = '1';
|
||||
$this->config('piwik.settings')->set('site_id', $site_id)->save();
|
||||
$this->config('piwik.settings')->set('url_http', 'http://www.example.com/piwik/')->save();
|
||||
$this->config('piwik.settings')->set('url_https', 'https://www.example.com/piwik/')->save();
|
||||
|
||||
$this->drupalGet('user/password', ['query' => ['name' => 'foo']]);
|
||||
$this->assertRaw('_paq.push(["setCustomUrl", ' . Json::encode($base_path . 'user/password') . ']);');
|
||||
|
||||
$this->drupalGet('user/password', ['query' => ['name' => 'foo@example.com']]);
|
||||
$this->assertRaw('_paq.push(["setCustomUrl", ' . Json::encode($base_path . 'user/password') . ']);');
|
||||
|
||||
$this->drupalGet('user/password');
|
||||
$this->assertNoRaw('_paq.push(["setCustomUrl", "', '[testPiwikCustomUrls]: Custom url not set.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Tests;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Test custom variables functionality of Piwik module.
|
||||
*
|
||||
* @group Piwik
|
||||
*/
|
||||
class PiwikCustomVariablesTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['piwik', 'token'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer piwik',
|
||||
];
|
||||
|
||||
// User to set up piwik.
|
||||
$this->admin_user = $this->drupalCreateUser($permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if custom variables are properly added to the page.
|
||||
*/
|
||||
public function testPiwikCustomVariables() {
|
||||
$site_id = '3';
|
||||
$this->config('piwik.settings')->set('site_id', $site_id)->save();
|
||||
$this->config('piwik.settings')->set('url_http', 'http://www.example.com/piwik/')->save();
|
||||
$this->config('piwik.settings')->set('url_https', 'https://www.example.com/piwik/')->save();
|
||||
|
||||
// Basic test if the feature works.
|
||||
$custom_vars = [
|
||||
1 => [
|
||||
'slot' => 1,
|
||||
'name' => 'Foo 1',
|
||||
'value' => 'Bar 1',
|
||||
'scope' => 'visit',
|
||||
],
|
||||
2 => [
|
||||
'slot' => 2,
|
||||
'name' => 'Foo 2',
|
||||
'value' => 'Bar 2',
|
||||
'scope' => 'page',
|
||||
],
|
||||
3 => [
|
||||
'slot' => 3,
|
||||
'name' => 'Foo 3',
|
||||
'value' => 'Bar 3',
|
||||
'scope' => 'page',
|
||||
],
|
||||
4 => [
|
||||
'slot' => 4,
|
||||
'name' => 'Foo 4',
|
||||
'value' => 'Bar 4',
|
||||
'scope' => 'visit',
|
||||
],
|
||||
5 => [
|
||||
'slot' => 5,
|
||||
'name' => 'Foo 5',
|
||||
'value' => 'Bar 5',
|
||||
'scope' => 'visit',
|
||||
],
|
||||
];
|
||||
$this->config('piwik.settings')->set('custom.variable', $custom_vars)->save();
|
||||
$this->drupalGet('');
|
||||
|
||||
foreach ($custom_vars as $slot) {
|
||||
$this->assertRaw('_paq.push(["setCustomVariable", ' . Json::encode($slot['slot']) . ', ' . Json::encode($slot['name']) . ', ' . Json::encode($slot['value']) . ', ' . Json::encode($slot['scope']) . ']);', '[testPiwikCustomVariables]: setCustomVariable ' . $slot['slot'] . ' is shown.');
|
||||
}
|
||||
|
||||
// Test whether tokens are replaced in custom variable names.
|
||||
$site_slogan = $this->randomMachineName(16);
|
||||
$this->config('system.site')->set('slogan', $site_slogan)->save();
|
||||
|
||||
$custom_vars = [
|
||||
1 => [
|
||||
'slot' => 1,
|
||||
'name' => 'Name: [site:slogan]',
|
||||
'value' => 'Value: [site:slogan]',
|
||||
'scope' => 'visit',
|
||||
],
|
||||
2 => [
|
||||
'slot' => 2,
|
||||
'name' => '',
|
||||
'value' => $this->randomMachineName(16),
|
||||
'scope' => 'page',
|
||||
],
|
||||
3 => [
|
||||
'slot' => 3,
|
||||
'name' => $this->randomMachineName(16),
|
||||
'value' => '',
|
||||
'scope' => 'visit',
|
||||
],
|
||||
4 => [
|
||||
'slot' => 4,
|
||||
'name' => '',
|
||||
'value' => '',
|
||||
'scope' => 'page',
|
||||
],
|
||||
5 => [
|
||||
'slot' => 5,
|
||||
'name' => '',
|
||||
'value' => '',
|
||||
'scope' => 'visit',
|
||||
],
|
||||
];
|
||||
$this->config('piwik.settings')->set('custom.variable', $custom_vars)->save();
|
||||
$this->verbose('<pre>' . print_r($custom_vars, TRUE) . '</pre>');
|
||||
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw('_paq.push(["setCustomVariable", 1, ' . Json::encode("Name: $site_slogan") . ', ' . Json::encode("Value: $site_slogan") . ', "visit"]', '[testPiwikCustomVariables]: Tokens have been replaced in custom variable.');
|
||||
$this->assertNoRaw('_paq.push(["setCustomVariable", 2,', '[testPiwikCustomVariables]: Value with empty name is not shown.');
|
||||
$this->assertNoRaw('_paq.push(["setCustomVariable", 3,', '[testPiwikCustomVariables]: Name with empty value is not shown.');
|
||||
$this->assertNoRaw('_paq.push(["setCustomVariable", 4,', '[testPiwikCustomVariables]: Empty name and value is not shown.');
|
||||
$this->assertNoRaw('_paq.push(["setCustomVariable", 5,', '[testPiwikCustomVariables]: Empty name and value is not shown.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Tests;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Test php filter functionality of Piwik module.
|
||||
*
|
||||
* @group Piwik
|
||||
*/
|
||||
class PiwikPhpFilterTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['piwik', 'php'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Administrator with all permissions.
|
||||
$permissions_admin_user = [
|
||||
'access administration pages',
|
||||
'administer piwik',
|
||||
'use PHP for piwik tracking visibility',
|
||||
];
|
||||
$this->admin_user = $this->drupalCreateUser($permissions_admin_user);
|
||||
|
||||
// Administrator who cannot configure tracking visibility with PHP.
|
||||
$permissions_delegated_admin_user = [
|
||||
'access administration pages',
|
||||
'administer piwik',
|
||||
];
|
||||
$this->delegated_admin_user = $this->drupalCreateUser($permissions_delegated_admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if PHP module integration works.
|
||||
*/
|
||||
public function testPiwikPhpFilter() {
|
||||
$site_id = '1';
|
||||
$this->drupalLogin($this->admin_user);
|
||||
|
||||
$edit = [];
|
||||
$edit['piwik_site_id'] = $site_id;
|
||||
$edit['piwik_url_http'] = 'http://www.example.com/piwik/';
|
||||
$edit['piwik_url_https'] = 'https://www.example.com/piwik/';
|
||||
// Skip url check errors in automated tests.
|
||||
$edit['piwik_url_skiperror'] = TRUE;
|
||||
$edit['piwik_visibility_request_path_mode'] = 2;
|
||||
$edit['piwik_visibility_request_path_pages'] = '<?php return 0; ?>';
|
||||
$this->drupalPostForm('admin/config/system/piwik', $edit, t('Save configuration'));
|
||||
|
||||
// Compare saved setting with posted setting.
|
||||
$piwik_visibility_request_path_pages = \Drupal::config('piwik.settings')->get('visibility.request_path_pages');
|
||||
$this->assertEqual('<?php return 0; ?>', $piwik_visibility_request_path_pages, '[testPiwikPhpFilter]: PHP code snippet is intact.');
|
||||
|
||||
// Check tracking code visibility.
|
||||
$this->config('piwik.settings')->set('visibility.request_path_pages', '<?php return TRUE; ?>')->save();
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw('u+"piwik.php"', '[testPiwikPhpFilter]: Tracking is displayed on frontpage page.');
|
||||
$this->drupalGet('admin');
|
||||
$this->assertRaw('u+"piwik.php"', '[testPiwikPhpFilter]: Tracking is displayed on admin page.');
|
||||
|
||||
$this->config('piwik.settings')->set('visibility.request_path_pages', '<?php return FALSE; ?>')->save();
|
||||
$this->drupalGet('');
|
||||
$this->assertNoRaw('u+"piwik.php"', '[testPiwikPhpFilter]: Tracking is not displayed on frontpage page.');
|
||||
|
||||
// Test administration form.
|
||||
$this->config('piwik.settings')->set('visibility.request_path_pages', '<?php return TRUE; ?>')->save();
|
||||
$this->drupalGet('admin/config/system/piwik');
|
||||
$this->assertRaw(t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'), '[testPiwikPhpFilter]: Permission to administer PHP for tracking visibility.');
|
||||
$this->assertRaw(Html::escape('<?php return TRUE; ?>'), '[testPiwikPhpFilter]: PHP code snippted is displayed.');
|
||||
|
||||
// Login the delegated user and check if fields are visible.
|
||||
$this->drupalLogin($this->delegated_admin_user);
|
||||
$this->drupalGet('admin/config/system/piwik');
|
||||
$this->assertNoRaw(t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'), '[testPiwikPhpFilter]: No permission to administer PHP for tracking visibility.');
|
||||
$this->assertRaw(Html::escape('<?php return TRUE; ?>'), '[testPiwikPhpFilter]: No permission to view PHP code snippted.');
|
||||
|
||||
// Set a different value and verify that this is still the same after the
|
||||
// post.
|
||||
$this->config('piwik.settings')->set('visibility.request_path_pages', '<?php return 0; ?>')->save();
|
||||
|
||||
$edit = [];
|
||||
$edit['piwik_site_id'] = $site_id;
|
||||
$edit['piwik_url_http'] = 'http://www.example.com/piwik/';
|
||||
$edit['piwik_url_https'] = 'https://www.example.com/piwik/';
|
||||
// Required for testing only.
|
||||
$edit['piwik_url_skiperror'] = TRUE;
|
||||
$this->drupalPostForm('admin/config/system/piwik', $edit, t('Save configuration'));
|
||||
|
||||
// Compare saved setting with posted setting.
|
||||
$piwik_visibility_request_path_mode = $this->config('piwik.settings')->get('visibility.request_path_mode');
|
||||
$piwik_visibility_request_path_pages = $this->config('piwik.settings')->get('visibility.request_path_pages');
|
||||
$this->assertEqual(2, $piwik_visibility_request_path_mode, '[testPiwikPhpFilter]: Pages on which this PHP code returns TRUE is selected.');
|
||||
$this->assertEqual('<?php return 0; ?>', $piwik_visibility_request_path_pages, '[testPiwikPhpFilter]: PHP code snippet is intact.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Tests;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Test roles functionality of Piwik module.
|
||||
*
|
||||
* @group Piwik
|
||||
*/
|
||||
class PiwikRolesTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['piwik'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer piwik',
|
||||
];
|
||||
|
||||
// User to set up piwik.
|
||||
$this->admin_user = $this->drupalCreateUser($permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if roles based tracking works.
|
||||
*/
|
||||
public function testPiwikRolesTracking() {
|
||||
$site_id = '1';
|
||||
$this->config('piwik.settings')->set('site_id', $site_id)->save();
|
||||
$this->config('piwik.settings')->set('url_http', 'http://www.example.com/piwik/')->save();
|
||||
$this->config('piwik.settings')->set('url_https', 'https://www.example.com/piwik/')->save();
|
||||
|
||||
// Test if the default settings are working as expected.
|
||||
// Add to the selected roles only.
|
||||
$this->config('piwik.settings')->set('visibility.user_role_mode', 0)->save();
|
||||
// Enable tracking for all users.
|
||||
$this->config('piwik.settings')->set('visibility.user_role_roles', [])->save();
|
||||
|
||||
// Check tracking code visibility.
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw('u+"piwik.php"', '[testPiwikRoleVisibility]: Tracking code is displayed for anonymous users on frontpage with default settings.');
|
||||
$this->drupalGet('admin');
|
||||
$this->assertRaw('"403/URL = "', '[testPiwikRoleVisibility]: 403 Forbidden tracking code is displayed for anonymous users in admin section with default settings.');
|
||||
|
||||
$this->drupalLogin($this->admin_user);
|
||||
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw('u+"piwik.php"', '[testPiwikRoleVisibility]: Tracking code is displayed for authenticated users on frontpage with default settings.');
|
||||
$this->drupalGet('admin');
|
||||
$this->assertNoRaw('u+"piwik.php"', '[testPiwikRoleVisibility]: Tracking code is NOT displayed for authenticated users in admin section with default settings.');
|
||||
|
||||
// Test if the non-default settings are working as expected.
|
||||
// Enable tracking only for authenticated users.
|
||||
$this->config('piwik.settings')->set('visibility.user_role_roles', [AccountInterface::AUTHENTICATED_ROLE => AccountInterface::AUTHENTICATED_ROLE])->save();
|
||||
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw('u+"piwik.php"', '[testPiwikRoleVisibility]: Tracking code is displayed for authenticated users only on frontpage.');
|
||||
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('');
|
||||
$this->assertNoRaw('u+"piwik.php"', '[testPiwikRoleVisibility]: Tracking code is NOT displayed for anonymous users on frontpage.');
|
||||
|
||||
// Add to every role except the selected ones.
|
||||
$this->config('piwik.settings')->set('visibility.user_role_mode', 1)->save();
|
||||
// Enable tracking for all users.
|
||||
$this->config('piwik.settings')->set('visibility.user_role_roles', [])->save();
|
||||
|
||||
// Check tracking code visibility.
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw('u+"piwik.php"', '[testPiwikRoleVisibility]: Tracking code is added to every role and displayed for anonymous users.');
|
||||
$this->drupalGet('admin');
|
||||
$this->assertRaw('"403/URL = "', '[testPiwikRoleVisibility]: 403 Forbidden tracking code is shown for anonymous users if every role except the selected ones is selected.');
|
||||
|
||||
$this->drupalLogin($this->admin_user);
|
||||
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw('u+"piwik.php"', '[testPiwikRoleVisibility]: Tracking code is added to every role and displayed on frontpage for authenticated users.');
|
||||
$this->drupalGet('admin');
|
||||
$this->assertNoRaw('u+"piwik.php"', '[testPiwikRoleVisibility]: Tracking code is added to every role and NOT displayed in admin section for authenticated users.');
|
||||
|
||||
// Disable tracking for authenticated users.
|
||||
$this->config('piwik.settings')->set('visibility.user_role_roles', [AccountInterface::AUTHENTICATED_ROLE => AccountInterface::AUTHENTICATED_ROLE])->save();
|
||||
|
||||
$this->drupalGet('');
|
||||
$this->assertNoRaw('u+"piwik.php"', '[testPiwikRoleVisibility]: Tracking code is NOT displayed on frontpage for excluded authenticated users.');
|
||||
$this->drupalGet('admin');
|
||||
$this->assertNoRaw('u+"piwik.php"', '[testPiwikRoleVisibility]: Tracking code is NOT displayed in admin section for excluded authenticated users.');
|
||||
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw('u+"piwik.php"', '[testPiwikRoleVisibility]: Tracking code is displayed on frontpage for included anonymous users.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Test search functionality of Piwik module.
|
||||
*
|
||||
* @group Piwik
|
||||
*/
|
||||
class PiwikSearchTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['piwik', 'search', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
|
||||
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer piwik',
|
||||
'search content',
|
||||
'create page content',
|
||||
'edit own page content',
|
||||
];
|
||||
|
||||
// User to set up piwik.
|
||||
$this->admin_user = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($this->admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if search tracking is properly added to the page.
|
||||
*/
|
||||
public function testPiwikSearchTracking() {
|
||||
$site_id = '1';
|
||||
$this->config('piwik.settings')->set('site_id', $site_id)->save();
|
||||
$this->config('piwik.settings')->set('url_http', 'http://www.example.com/piwik/')->save();
|
||||
$this->config('piwik.settings')->set('url_https', 'https://www.example.com/piwik/')->save();
|
||||
|
||||
// Check tracking code visibility.
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw($site_id, '[testPiwikSearch]: Tracking code is displayed for authenticated users.');
|
||||
|
||||
$this->drupalGet('search/node');
|
||||
$this->assertNoRaw('_paq.push(["trackSiteSearch", ', '[testPiwikSearch]: Search tracker not added to page.');
|
||||
|
||||
// Enable site search support.
|
||||
$this->config('piwik.settings')->set('track.site_search', 1)->save();
|
||||
|
||||
// Search for random string.
|
||||
$search = [];
|
||||
$search['keys'] = $this->randomMachineName(8);
|
||||
|
||||
// Create a node to search for.
|
||||
// Create a node.
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = 'This is a test title';
|
||||
$edit['body[0][value]'] = 'This test content contains ' . $search['keys'] . ' string.';
|
||||
|
||||
// Fire a search, it's expected to get 0 results.
|
||||
$this->drupalPostForm('search/node', $search, t('Search'));
|
||||
$this->assertRaw('_paq.push(["trackSiteSearch", ', '[testPiwikSearch]: Search results tracker is displayed.');
|
||||
$this->assertRaw('window.piwik_search_results = 0;', '[testPiwikSearch]: Search yielded no results.');
|
||||
|
||||
// Save the node.
|
||||
$this->drupalPostForm('node/add/page', $edit, t('Save'));
|
||||
$this->assertText(t('@type @title has been created.', ['@type' => 'Basic page', '@title' => $edit['title[0][value]']]), 'Basic page created.');
|
||||
|
||||
// Index the node or it cannot found.
|
||||
$this->cronRun();
|
||||
|
||||
$this->drupalPostForm('search/node', $search, t('Search'));
|
||||
$this->assertRaw('_paq.push(["trackSiteSearch", ', '[testPiwikSearch]: Search results tracker is displayed.');
|
||||
$this->assertRaw('window.piwik_search_results = 1;', '[testPiwikSearch]: One search result found.');
|
||||
|
||||
$this->drupalPostForm('node/add/page', $edit, t('Save'));
|
||||
$this->assertText(t('@type @title has been created.', ['@type' => 'Basic page', '@title' => $edit['title[0][value]']]), 'Basic page created.');
|
||||
|
||||
// Index the node or it cannot found.
|
||||
$this->cronRun();
|
||||
|
||||
$this->drupalPostForm('search/node', $search, t('Search'));
|
||||
$this->assertRaw('_paq.push(["trackSiteSearch", ', '[testPiwikSearch]: Search results tracker is displayed.');
|
||||
$this->assertRaw('window.piwik_search_results = 2;', '[testPiwikSearch]: Two search results found.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Test status messages functionality of Piwik module.
|
||||
*
|
||||
* @group Piwik
|
||||
*/
|
||||
class PiwikStatusMessagesTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['piwik', 'piwik_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer piwik',
|
||||
];
|
||||
|
||||
// User to set up piwik.
|
||||
$this->admin_user = $this->drupalCreateUser($permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if status messages tracking is properly added to the page.
|
||||
*/
|
||||
public function testPiwikStatusMessages() {
|
||||
$site_id = '1';
|
||||
$this->config('piwik.settings')->set('site_id', $site_id)->save();
|
||||
$this->config('piwik.settings')->set('url_http', 'http://www.example.com/piwik/')->save();
|
||||
$this->config('piwik.settings')->set('url_https', 'https://www.example.com/piwik/')->save();
|
||||
|
||||
// Enable logging of errors only.
|
||||
$this->config('piwik.settings')->set('track.messages', ['error' => 'error'])->save();
|
||||
|
||||
$this->drupalPostForm('user/login', [], t('Log in'));
|
||||
$this->assertRaw('_paq.push(["trackEvent", "Messages", "Error message", "Username field is required."]);', '[testPiwikStatusMessages]: trackEvent "Username field is required." is shown.');
|
||||
$this->assertRaw('_paq.push(["trackEvent", "Messages", "Error message", "Password field is required."]);', '[testPiwikStatusMessages]: trackEvent "Password field is required." is shown.');
|
||||
|
||||
// Testing this drupal_set_message() requires an extra test module.
|
||||
$this->drupalGet('piwik-test/drupal-set-message');
|
||||
$this->assertNoRaw('_paq.push(["trackEvent", "Messages", "Status message", "Example status message."]);', '[testPiwikStatusMessages]: Example status message is not enabled for tracking.');
|
||||
$this->assertNoRaw('_paq.push(["trackEvent", "Messages", "Warning message", "Example warning message."]);', '[testPiwikStatusMessages]: Example warning message is not enabled for tracking.');
|
||||
$this->assertRaw('_paq.push(["trackEvent", "Messages", "Error message", "Example error message."]);', '[testPiwikStatusMessages]: Example error message is shown.');
|
||||
$this->assertRaw('_paq.push(["trackEvent", "Messages", "Error message", "Example error message with html tags and link."]);', '[testPiwikStatusMessages]: HTML has been stripped successful from Example error message with html tags and link.');
|
||||
|
||||
// Enable logging of status, warnings and errors.
|
||||
$this->config('piwik.settings')->set('track.messages', ['status' => 'status', 'warning' => 'warning', 'error' => 'error'])->save();
|
||||
|
||||
$this->drupalGet('piwik-test/drupal-set-message');
|
||||
$this->assertRaw('_paq.push(["trackEvent", "Messages", "Status message", "Example status message."]);', '[testPiwikStatusMessages]: Example status message is enabled for tracking.');
|
||||
$this->assertRaw('_paq.push(["trackEvent", "Messages", "Warning message", "Example warning message."]);', '[testPiwikStatusMessages]: Example warning message is enabled for tracking.');
|
||||
$this->assertRaw('_paq.push(["trackEvent", "Messages", "Error message", "Example error message."]);', '[testPiwikStatusMessages]: Example error message is shown.');
|
||||
$this->assertRaw('_paq.push(["trackEvent", "Messages", "Error message", "Example error message with html tags and link."]);', '[testPiwikStatusMessages]: HTML has been stripped successful from Example error message with html tags and link.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Test uninstall functionality of Piwik module.
|
||||
*
|
||||
* @group Piwik
|
||||
*/
|
||||
class PiwikUninstallTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['piwik'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer piwik',
|
||||
'administer modules',
|
||||
];
|
||||
|
||||
// User to set up piwik.
|
||||
$this->admin_user = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($this->admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the module cleans up the disk on uninstall.
|
||||
*/
|
||||
public function testPiwikUninstall() {
|
||||
$cache_path = 'public://piwik';
|
||||
$site_id = '1';
|
||||
$this->config('piwik.settings')->set('site_id', $site_id)->save();
|
||||
$this->config('piwik.settings')->set('url_http', 'http://www.example.com/piwik/')->save();
|
||||
$this->config('piwik.settings')->set('url_https', 'https://www.example.com/piwik/')->save();
|
||||
|
||||
// Enable local caching of piwik.js.
|
||||
$this->config('piwik.settings')->set('cache', 1)->save();
|
||||
|
||||
// Load front page to get the piwik.js downloaded into local cache. But
|
||||
// loading the piwik.js is not possible as "url_http" is a test dummy only.
|
||||
// Create a dummy file to complete the rest of the tests.
|
||||
file_prepare_directory($cache_path, FILE_CREATE_DIRECTORY);
|
||||
$data = $this->randomMachineName(128);
|
||||
$file_destination = $cache_path . '/piwik.js';
|
||||
file_unmanaged_save_data($data, $file_destination);
|
||||
file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $file_destination . '.gz', FILE_EXISTS_REPLACE);
|
||||
|
||||
// Test if the directory and piwik.js exists.
|
||||
$this->assertTrue(file_prepare_directory($cache_path), 'Cache directory "public://piwik" has been found.');
|
||||
$this->assertTrue(file_exists($cache_path . '/piwik.js'), 'Cached piwik.js tracking file has been found.');
|
||||
$this->assertTrue(file_exists($cache_path . '/piwik.js.gz'), 'Cached piwik.js.gz tracking file has been found.');
|
||||
|
||||
// Uninstall the module.
|
||||
$edit = [];
|
||||
$edit['uninstall[piwik]'] = TRUE;
|
||||
$this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall'));
|
||||
$this->assertNoText(\Drupal::translation()->translate('Configuration deletions'), 'No configuration deletions listed on the module install confirmation page.');
|
||||
$this->drupalPostForm(NULL, NULL, t('Uninstall'));
|
||||
$this->assertText(t('The selected modules have been uninstalled.'), 'Modules status has been updated.');
|
||||
|
||||
// Test if the directory and all files have been removed.
|
||||
$this->assertFalse(file_scan_directory($cache_path, '/.*/'), 'Cached JavaScript files have been removed.');
|
||||
$this->assertFalse(file_prepare_directory($cache_path), 'Cache directory "public://piwik" has been removed.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Test user fields functionality of Piwik module.
|
||||
*
|
||||
* @group Piwik
|
||||
*/
|
||||
class PiwikUserFieldsTestTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['piwik', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer user form display',
|
||||
'opt-in or out of piwik tracking',
|
||||
];
|
||||
|
||||
// User to set up piwik.
|
||||
$this->admin_user = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($this->admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if "allow users to customize tracking on their account page" works.
|
||||
*/
|
||||
public function testPiwikUserFields() {
|
||||
$ua_code = 'UA-123456-1';
|
||||
$this->config('piwik.settings')->set('account', $ua_code)->save();
|
||||
|
||||
// Check if the pseudo field is shown on account forms.
|
||||
$this->drupalGet('admin/config/people/accounts/form-display');
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw(t('Piwik settings'), '[testPiwikUserFields]: Piwik settings field exists on Manage form display.');
|
||||
|
||||
// No customization allowed.
|
||||
$this->config('piwik.settings')->set('visibility.user_account_mode', 0)->save();
|
||||
$this->drupalGet('user/' . $this->admin_user->id() . '/edit');
|
||||
$this->assertResponse(200);
|
||||
$this->assertNoRaw(t('Piwik settings'), '[testPiwikUserFields]: Piwik settings field does not exist on user edit page.');
|
||||
|
||||
// Tracking on by default, users with opt-in or out of tracking permission
|
||||
// can opt out.
|
||||
$this->config('piwik.settings')->set('visibility.user_account_mode', 1)->save();
|
||||
$this->drupalGet('user/' . $this->admin_user->id() . '/edit');
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw(t('Users are tracked by default, but you are able to opt out.'), '[testPiwikUserFields]: Piwik settings field exists on on user edit page');
|
||||
|
||||
// Tracking off by default, users with opt-in or out of tracking permission
|
||||
// can opt in.
|
||||
$this->config('piwik.settings')->set('visibility.user_account_mode', 2)->save();
|
||||
$this->drupalGet('user/' . $this->admin_user->id() . '/edit');
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw(t('Users are <em>not</em> tracked by default, but you are able to opt in.'), '[testPiwikUserFields]: Piwik settings field exists on on user edit page.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
name: 'Piwik test'
|
||||
type: module
|
||||
description: 'Support module for Piwik testing.'
|
||||
package: Testing
|
||||
# core: 8.x
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-11-11
|
||||
version: '8.x-1.2'
|
||||
core: '8.x'
|
||||
project: 'piwik'
|
||||
datestamp: 1510404815
|
||||
@@ -0,0 +1,7 @@
|
||||
piwik_test.drupal_set_message:
|
||||
path: '/piwik-test/drupal-set-message'
|
||||
defaults:
|
||||
_title: 'Set messages with drupal_set_message()'
|
||||
_controller: '\Drupal\piwik_test\Controller\PiwikTestController::drupalSetMessageTest'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\piwik_test\Controller;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
|
||||
/**
|
||||
* Controller routines for system_test routes.
|
||||
*/
|
||||
class PiwikTestController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Tests setting messages and removing one before it is displayed.
|
||||
*
|
||||
* @return string
|
||||
* Empty string, we just test the setting of messages.
|
||||
*/
|
||||
public function drupalSetMessageTest() {
|
||||
// Set some messages.
|
||||
drupal_set_message('Example status message.', 'status');
|
||||
drupal_set_message('Example warning message.', 'warning');
|
||||
drupal_set_message('Example error message.', 'error');
|
||||
drupal_set_message('Example error <em>message</em> with html tags and <a href="http://example.com/">link</a>.', 'error');
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,3 @@
|
||||
|
||||
This is the new module home for a unified redirection API (also replaces
|
||||
path_redirect and globalredirect).
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "drupal/redirect",
|
||||
"description": "Allows users to redirect from old URLs to new URLs.",
|
||||
"type": "drupal-module",
|
||||
"license": "GPL-2.0+",
|
||||
"minimum-stability": "dev",
|
||||
"require": {
|
||||
"drupal/core": "~8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
auto_redirect: true
|
||||
default_status_code: 301
|
||||
passthrough_querystring: true
|
||||
warning: false
|
||||
ignore_admin_path: false
|
||||
access_check: false
|
||||
route_normalizer_enabled: true
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
enforced:
|
||||
module:
|
||||
- redirect
|
||||
id: redirect_delete_action
|
||||
label: 'Delete redirect'
|
||||
type: redirect
|
||||
plugin: redirect_delete_action
|
||||
configuration: { }
|
||||
@@ -0,0 +1,593 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- link
|
||||
- redirect
|
||||
- user
|
||||
id: redirect
|
||||
label: Redirect
|
||||
module: views
|
||||
description: 'List of redirects'
|
||||
tag: ''
|
||||
base_table: redirect
|
||||
base_field: rid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'administer redirects'
|
||||
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: 50
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
tags:
|
||||
previous: '‹ previous'
|
||||
next: 'next ›'
|
||||
first: '« first'
|
||||
last: 'last »'
|
||||
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
|
||||
quantity: 9
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
override: true
|
||||
sticky: false
|
||||
caption: ''
|
||||
summary: ''
|
||||
description: ''
|
||||
columns:
|
||||
redirect_source__path: redirect_source__path
|
||||
redirect_redirect__uri: redirect_redirect__uri
|
||||
status_code: status_code
|
||||
language: language
|
||||
created: created
|
||||
operations: operations
|
||||
info:
|
||||
redirect_source__path:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
redirect_redirect__uri:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
status_code:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
language:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
created:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
operations:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
default: created
|
||||
empty_table: false
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
redirect_bulk_form:
|
||||
id: redirect_bulk_form
|
||||
table: redirect
|
||||
field: redirect_bulk_form
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
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
|
||||
action_title: 'With selection'
|
||||
include_exclude: exclude
|
||||
selected_actions: { }
|
||||
entity_type: redirect
|
||||
plugin_id: redirect_bulk_form
|
||||
redirect_source__path:
|
||||
id: redirect_source__path
|
||||
table: redirect
|
||||
field: redirect_source__path
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: From
|
||||
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: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: path
|
||||
type: redirect_source
|
||||
settings: { }
|
||||
group_column: ''
|
||||
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
|
||||
entity_type: redirect
|
||||
entity_field: redirect_source
|
||||
plugin_id: field
|
||||
redirect_redirect__uri:
|
||||
id: redirect_redirect__uri
|
||||
table: redirect
|
||||
field: redirect_redirect__uri
|
||||
entity_type: redirect
|
||||
entity_field: redirect_redirect
|
||||
plugin_id: field
|
||||
status_code:
|
||||
id: status_code
|
||||
table: redirect
|
||||
field: status_code
|
||||
entity_type: redirect
|
||||
entity_field: status_code
|
||||
plugin_id: field
|
||||
language:
|
||||
id: language
|
||||
table: redirect
|
||||
field: language
|
||||
entity_type: redirect
|
||||
entity_field: language
|
||||
plugin_id: field
|
||||
created:
|
||||
id: created
|
||||
table: redirect
|
||||
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: true
|
||||
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: ''
|
||||
entity_type: redirect
|
||||
entity_field: created
|
||||
plugin_id: date
|
||||
operations:
|
||||
id: operations
|
||||
table: redirect
|
||||
field: operations
|
||||
entity_type: redirect
|
||||
plugin_id: entity_operations
|
||||
filters:
|
||||
redirect_source__path:
|
||||
id: redirect_source__path
|
||||
table: redirect
|
||||
field: redirect_source__path
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: contains
|
||||
value: ''
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: redirect_source__path_op
|
||||
label: From
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: redirect_source__path_op
|
||||
identifier: redirect_source__path
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '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: { }
|
||||
entity_type: redirect
|
||||
entity_field: redirect_source
|
||||
plugin_id: string
|
||||
redirect_redirect__uri:
|
||||
id: redirect_redirect__uri
|
||||
table: redirect
|
||||
field: redirect_redirect__uri
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: contains
|
||||
value: ''
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: redirect_redirect__uri_op
|
||||
label: To
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: redirect_redirect__uri_op
|
||||
identifier: redirect_redirect__uri
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '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: { }
|
||||
entity_type: redirect
|
||||
entity_field: redirect_redirect
|
||||
plugin_id: string
|
||||
status_code:
|
||||
id: status_code
|
||||
table: redirect
|
||||
field: status_code
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: '='
|
||||
value:
|
||||
min: ''
|
||||
max: ''
|
||||
value: ''
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: status_code_op
|
||||
label: 'Status code'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: status_code_op
|
||||
identifier: status_code
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
is_grouped: true
|
||||
group_info:
|
||||
label: 'Status code'
|
||||
description: ''
|
||||
identifier: status_code
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items:
|
||||
1:
|
||||
title: '300 Multiple Choices'
|
||||
operator: '='
|
||||
value:
|
||||
value: '300'
|
||||
min: ''
|
||||
max: ''
|
||||
2:
|
||||
title: '301 Moved Permanently'
|
||||
operator: '='
|
||||
value:
|
||||
value: '301'
|
||||
min: ''
|
||||
max: ''
|
||||
3:
|
||||
title: '302 Found'
|
||||
operator: '='
|
||||
value:
|
||||
value: '302'
|
||||
min: ''
|
||||
max: ''
|
||||
4:
|
||||
title: '303 See Other'
|
||||
operator: '='
|
||||
value:
|
||||
value: '303'
|
||||
min: ''
|
||||
max: ''
|
||||
5:
|
||||
title: '304 Not Modified'
|
||||
operator: '='
|
||||
value:
|
||||
value: '304'
|
||||
min: ''
|
||||
max: ''
|
||||
6:
|
||||
title: '305 Use Proxy'
|
||||
operator: '='
|
||||
value:
|
||||
value: '305'
|
||||
min: ''
|
||||
max: ''
|
||||
7:
|
||||
title: '307 Temporary Redirect'
|
||||
operator: '='
|
||||
value:
|
||||
value: '307'
|
||||
min: ''
|
||||
max: ''
|
||||
entity_type: redirect
|
||||
entity_field: status_code
|
||||
plugin_id: numeric
|
||||
language:
|
||||
id: language
|
||||
table: redirect
|
||||
field: language
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: language_op
|
||||
label: 'Original language'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: language_op
|
||||
identifier: language
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '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: { }
|
||||
entity_type: redirect
|
||||
entity_field: language
|
||||
plugin_id: language
|
||||
sorts: { }
|
||||
title: Redirect
|
||||
header: { }
|
||||
footer: { }
|
||||
empty:
|
||||
area_text_custom:
|
||||
id: area_text_custom
|
||||
table: views
|
||||
field: area_text_custom
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
empty: true
|
||||
tokenize: false
|
||||
content: 'There is no redirect yet.'
|
||||
plugin_id: text_custom
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups:
|
||||
1: AND
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
cacheable: false
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: admin/config/search/redirect
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
cacheable: false
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
status: true
|
||||
dependencies: { }
|
||||
id: redirect.redirect
|
||||
target_entity_type_id: redirect
|
||||
target_bundle: redirect
|
||||
default_langcode: und
|
||||
language_alterable: true
|
||||
@@ -0,0 +1,31 @@
|
||||
# Schema for the configuration files of the Redirect module.
|
||||
|
||||
redirect.settings:
|
||||
type: config_entity
|
||||
label: 'Redirect settings'
|
||||
mapping:
|
||||
auto_redirect:
|
||||
type: boolean
|
||||
label: 'Automatically create redirects when URL aliases are changed.'
|
||||
default_status_code:
|
||||
type: integer
|
||||
label: 'Default redirect status'
|
||||
passthrough_querystring:
|
||||
type: boolean
|
||||
label: 'Retain query string through redirect.'
|
||||
warning:
|
||||
type: boolean
|
||||
label: 'Display a warning message to users when they are redirected.'
|
||||
ignore_admin_path:
|
||||
type: boolean
|
||||
label: 'Allow redirections on admin paths.'
|
||||
access_check:
|
||||
type: boolean
|
||||
label: 'Menu Access Checking'
|
||||
route_normalizer_enabled:
|
||||
type: boolean
|
||||
label: 'Enforce clean and canonical URLs.'
|
||||
|
||||
action.configuration.redirect_delete_action:
|
||||
type: action_configuration_default
|
||||
label: 'Delete redirect configuration'
|
||||
@@ -0,0 +1,19 @@
|
||||
# Schema for the views plugins of the Redirect module.
|
||||
|
||||
views.field.redirect_type:
|
||||
type: views.field.redirect
|
||||
label: 'Redirect type'
|
||||
mapping:
|
||||
machine_name:
|
||||
type: string
|
||||
label: 'Output machine name'
|
||||
|
||||
# Schema for the formatter of Redirect Source
|
||||
field.formatter.settings.redirect_source:
|
||||
type: mapping
|
||||
label: 'Redirect Source formatter'
|
||||
mapping: { }
|
||||
|
||||
views.field.redirect_bulk_form:
|
||||
type: views_field_bulk_form
|
||||
label: 'Redirect bulk form'
|
||||
@@ -0,0 +1,14 @@
|
||||
.redirect-table {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.redirect-table__operations {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.redirect-table__path {
|
||||
width: 70%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
id: d6_path_redirect
|
||||
label: Path Redirect
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
source:
|
||||
plugin: d6_path_redirect
|
||||
process:
|
||||
rid: rid
|
||||
redirect_source: source
|
||||
redirect_redirect:
|
||||
plugin: d6_path_redirect
|
||||
source:
|
||||
- redirect
|
||||
- query
|
||||
language:
|
||||
plugin: default_value
|
||||
source: language
|
||||
default_value: und
|
||||
status_code: type
|
||||
destination:
|
||||
plugin: entity:redirect
|
||||
@@ -0,0 +1,25 @@
|
||||
id: d7_path_redirect
|
||||
label: Path Redirect
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
source:
|
||||
plugin: d7_path_redirect
|
||||
process:
|
||||
rid: rid
|
||||
uid: uid
|
||||
redirect_source/path: source
|
||||
redirect_source/query:
|
||||
plugin: d7_redirect_source_query
|
||||
source: source_options
|
||||
redirect_redirect/uri:
|
||||
plugin: d7_path_redirect
|
||||
source:
|
||||
- redirect
|
||||
- redirect_options
|
||||
language:
|
||||
plugin: default_value
|
||||
source: language
|
||||
default_value: und
|
||||
status_code: status_code
|
||||
destination:
|
||||
plugin: entity:redirect
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
row_limit: 10000
|
||||
pages: ''
|
||||
suppress_404: FALSE
|
||||
+523
@@ -0,0 +1,523 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- redirect_404
|
||||
- user
|
||||
id: redirect_404
|
||||
label: 'Redirect 404'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: redirect_404
|
||||
base_field: ''
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'administer redirects'
|
||||
cache:
|
||||
type: none
|
||||
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: Filter
|
||||
reset_button: true
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: mini
|
||||
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: ‹‹
|
||||
next: ››
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
override: true
|
||||
sticky: false
|
||||
caption: ''
|
||||
summary: ''
|
||||
description: ''
|
||||
columns:
|
||||
path: path
|
||||
count: count
|
||||
timestamp: timestamp
|
||||
info:
|
||||
path:
|
||||
sortable: true
|
||||
default_sort_order: desc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
count:
|
||||
sortable: true
|
||||
default_sort_order: desc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
timestamp:
|
||||
sortable: true
|
||||
default_sort_order: desc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
default: count
|
||||
empty_table: false
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
path:
|
||||
table: redirect_404
|
||||
field: path
|
||||
id: path
|
||||
entity_type: null
|
||||
entity_field: null
|
||||
plugin_id: standard
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Path
|
||||
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: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
count:
|
||||
id: count
|
||||
table: redirect_404
|
||||
field: count
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Count
|
||||
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: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
format: unserialized
|
||||
key: ''
|
||||
plugin_id: serialized
|
||||
timestamp:
|
||||
id: timestamp
|
||||
table: redirect_404
|
||||
field: timestamp
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Last accessed'
|
||||
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: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
date_format: short
|
||||
custom_date_format: ''
|
||||
timezone: ''
|
||||
plugin_id: date
|
||||
langcode:
|
||||
id: langcode
|
||||
table: redirect_404
|
||||
field: langcode
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Language
|
||||
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: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: standard
|
||||
redirect_404_operations:
|
||||
id: redirect_404_operations
|
||||
table: redirect_404
|
||||
field: redirect_404_operations
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Operations
|
||||
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: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: redirect_404_operations
|
||||
filters:
|
||||
path:
|
||||
id: path
|
||||
table: redirect_404
|
||||
field: path
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: contains
|
||||
value: ''
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: path_op
|
||||
label: Path
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: path_op
|
||||
identifier: path
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '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
|
||||
langcode:
|
||||
id: langcode
|
||||
table: redirect_404
|
||||
field: langcode
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: langcode_op
|
||||
label: Language
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: langcode_op
|
||||
identifier: langcode
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '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: { }
|
||||
plugin_id: in_operator
|
||||
resolved:
|
||||
id: resolved
|
||||
table: redirect_404
|
||||
field: resolved
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: '='
|
||||
value: '0'
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: Resolved
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: resolved_op
|
||||
identifier: resolved
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '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
|
||||
sorts: { }
|
||||
title: 'Fix 404 pages'
|
||||
header: { }
|
||||
footer: { }
|
||||
empty:
|
||||
area_text_custom:
|
||||
id: area_text_custom
|
||||
table: views
|
||||
field: area_text_custom
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
empty: true
|
||||
tokenize: false
|
||||
content: 'There are no 404 errors to fix.'
|
||||
plugin_id: text_custom
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups:
|
||||
1: AND
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
tags: { }
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: admin/config/search/redirect/404
|
||||
display_description: 'Lists 404 error paths with no redirect assigned yet.'
|
||||
enabled: true
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
tags: { }
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# Schema for the configuration files of the redirect_404 module.
|
||||
|
||||
redirect_404.settings:
|
||||
type: config_object
|
||||
label: '404 error database logging settings.'
|
||||
mapping:
|
||||
row_limit:
|
||||
type: integer
|
||||
label: '404 error database logs to keep.'
|
||||
pages:
|
||||
type: string
|
||||
suppress_404:
|
||||
type: boolean
|
||||
label: "Whether to log 'page not found' messages to the standard log or not."
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
views.field.redirect_404_operations:
|
||||
type: views_field
|
||||
label: 'Redirect 404 operations'
|
||||
mapping:
|
||||
text:
|
||||
type: label
|
||||
label: 'Redirect 404 operations'
|
||||
@@ -0,0 +1,14 @@
|
||||
name: 'Redirect 404'
|
||||
type: module
|
||||
description: 'Logs 404 errors and allows users to create redirects for often requested but missing pages.'
|
||||
# core: 8.x
|
||||
|
||||
dependencies:
|
||||
- redirect
|
||||
- views
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-03-12
|
||||
version: '8.x-1.1'
|
||||
core: '8.x'
|
||||
project: 'redirect'
|
||||
datestamp: 1520897884
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Update hooks for the redirect_404 module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\redirect_404\SqlRedirectNotFoundStorage;
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function redirect_404_schema() {
|
||||
$schema['redirect_404'] = [
|
||||
'description' => 'Stores 404 requests.',
|
||||
'fields' => [
|
||||
'path' => [
|
||||
'description' => 'The path of the request.',
|
||||
'type' => 'varchar',
|
||||
'length' => SqlRedirectNotFoundStorage::MAX_PATH_LENGTH,
|
||||
'not null' => TRUE,
|
||||
],
|
||||
'langcode' => [
|
||||
'description' => 'The language of this request.',
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 12,
|
||||
'not null' => TRUE,
|
||||
'default' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
],
|
||||
'count' => [
|
||||
'description' => 'The number of requests with that path and language.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
],
|
||||
'timestamp' => [
|
||||
'description' => 'The timestamp of the last request with that path and language.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
],
|
||||
'resolved' => [
|
||||
'description' => 'Boolean indicating whether or not this path has a redirect assigned.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
],
|
||||
],
|
||||
'primary key' => ['path', 'langcode'],
|
||||
];
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove relevancy field from the redirect_404 table.
|
||||
*/
|
||||
function redirect_404_update_8101() {
|
||||
\Drupal::database()->schema()->dropField('redirect_404', 'relevancy');
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
redirect_404.goto_fix_404:
|
||||
route_name: redirect_404.fix_404
|
||||
title: 'Fix 404 pages with URL redirects'
|
||||
appears_on:
|
||||
- 'dblog.page_not_found'
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
redirect_404.fix_404:
|
||||
title: 'Fix 404 pages'
|
||||
parent: redirect.list
|
||||
route_name: redirect_404.fix_404
|
||||
description: 'Add redirects for 404 pages.'
|
||||
menu_name: admin
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
redirect_404.fix_404:
|
||||
route_name: redirect_404.fix_404
|
||||
base_route: redirect.list
|
||||
title: Fix 404 pages
|
||||
weight: 30
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Module file for redirect_404.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\redirect\Entity\Redirect;
|
||||
|
||||
/**
|
||||
* Implements hook_cron().
|
||||
*
|
||||
* Adds clean up job to drop the irrelevant rows from the redirect_404 table.
|
||||
*/
|
||||
function redirect_404_cron() {
|
||||
/** @var \Drupal\redirect_404\SqlRedirectNotFoundStorage $redirect_storage */
|
||||
$redirect_storage = \Drupal::service('redirect.not_found_storage');
|
||||
$redirect_storage->purgeOldRequests();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter() for system_logging_settings().
|
||||
*/
|
||||
function redirect_404_form_redirect_settings_form_alter(&$form, FormStateInterface $form_state, $form_id) {
|
||||
$config = \Drupal::configFactory()->getEditable('redirect_404.settings');
|
||||
|
||||
$row_limits = [100, 1000, 10000, 100000, 1000000];
|
||||
$form['row_limit'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => t('404 error database logs to keep'),
|
||||
'#default_value' => $config->get('row_limit'),
|
||||
'#options' => [0 => t('All')] + array_combine($row_limits, $row_limits),
|
||||
'#description' => t('The maximum number of 404 error logs to keep in the database log. Requires a <a href=":cron">cron maintenance task</a>.', [':cron' => Url::fromRoute('system.status')->toString()])
|
||||
];
|
||||
|
||||
$ignored_pages = $config->get('pages');
|
||||
// Add a new path to be ignored, if there is an ignore argument in the query.
|
||||
if ($path_to_ignore = \Drupal::request()->query->get('ignore')) {
|
||||
$ignored_pages .= "\n" . $path_to_ignore;
|
||||
}
|
||||
|
||||
$form['ignore_pages'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => t('Pages to ignore'),
|
||||
'#default_value' => $ignored_pages,
|
||||
'#description' => t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. An example path is %user-wildcard for every user page. %front is the front page.", [
|
||||
'%user-wildcard' => '/user/*',
|
||||
'%front' => '<front>',
|
||||
]),
|
||||
];
|
||||
|
||||
$form['suppress_404'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t("Suppress 'page not found' log messages"),
|
||||
'#default_value' => $config->get('suppress_404'),
|
||||
'#description' => t("Prevents logging 'page not found' events. Can be safely enabled when redirect_404 module is used, which stores them separately, nothing else relies on those messages."),
|
||||
];
|
||||
|
||||
$form['#submit'][] = 'redirect_404_logging_settings_submit';
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission handler for system_logging_settings().
|
||||
*
|
||||
* @see redirect_404_form_redirect_settings_form_alter()
|
||||
*/
|
||||
function redirect_404_logging_settings_submit($form, FormStateInterface $form_state) {
|
||||
// Make sure to store the 'pages to ignore' with the leading slash.
|
||||
$ignore_pages = explode(PHP_EOL, $form_state->getValue('ignore_pages'));
|
||||
$pages = '';
|
||||
foreach ($ignore_pages as $page) {
|
||||
if (!empty($page)) {
|
||||
$pages .= '/' . ltrim($page, '/') . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
\Drupal::configFactory()
|
||||
->getEditable('redirect_404.settings')
|
||||
->set('row_limit', $form_state->getValue('row_limit'))
|
||||
->set('pages', $pages)
|
||||
->set('suppress_404', $form_state->getValue('suppress_404'))
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_presave() for redirect entities.
|
||||
*/
|
||||
function redirect_404_redirect_presave(Redirect $redirect) {
|
||||
$path = $redirect->getSourcePathWithQuery();
|
||||
$langcode = $redirect->get('language')->value;
|
||||
|
||||
// Mark a potentially existing log entry for this path as resolved.
|
||||
\Drupal::service('redirect.not_found_storage')->resolveLogRequest($path, $langcode);
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
redirect_404.fix_404:
|
||||
path: '/admin/config/search/redirect/404'
|
||||
defaults:
|
||||
_title: 'Fix 404 pages'
|
||||
_form: '\Drupal\redirect_404\Form\RedirectFix404Form'
|
||||
requirements:
|
||||
_permission: 'administer redirects'
|
||||
|
||||
redirect_404.ignore_404:
|
||||
path: '/admin/config/search/redirect/404/ignore'
|
||||
defaults:
|
||||
_controller: '\Drupal\redirect_404\Controller\Fix404IgnoreController::ignorePath'
|
||||
requirements:
|
||||
_permission: 'administer redirect settings'
|
||||
_csrf_token: 'TRUE'
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
services:
|
||||
redirect.404_subscriber:
|
||||
class: Drupal\redirect_404\EventSubscriber\Redirect404Subscriber
|
||||
arguments: ['@path.current', '@path.matcher', '@request_stack', '@language_manager', '@redirect.not_found_storage', '@config.factory']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
redirect.not_found_storage:
|
||||
class: Drupal\redirect_404\SqlRedirectNotFoundStorage
|
||||
arguments: ['@database', '@config.factory']
|
||||
tags:
|
||||
- { name: backend_overridable }
|
||||
logger.redirect_404:
|
||||
public: false
|
||||
class: \Drupal\redirect_404\Render\Redirect404LogSuppressor
|
||||
decorates: logger.factory
|
||||
arguments: ['@logger.redirect_404.inner', '@config.factory']
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provide views data for redirect_404.module.
|
||||
*/
|
||||
|
||||
use Drupal\redirect_404\SqlRedirectNotFoundStorage;
|
||||
|
||||
/**
|
||||
* Implements hook_views_data().
|
||||
*/
|
||||
function redirect_404_views_data() {
|
||||
$data = [];
|
||||
|
||||
// Only define views data if the service uses our specific implementation.
|
||||
if (!\Drupal::service('redirect.not_found_storage') instanceof SqlRedirectNotFoundStorage) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$data['redirect_404']['table']['group'] = t('Redirect 404');
|
||||
|
||||
$data['redirect_404']['table']['base'] = [
|
||||
'field' => '',
|
||||
'title' => t('Fix 404 pages'),
|
||||
'help' => t('Overview for 404 error paths with no redirect assigned yet.'),
|
||||
];
|
||||
|
||||
$data['redirect_404']['path'] = [
|
||||
'title' => t('Path'),
|
||||
'help' => t('The path of the request.'),
|
||||
'field' => [
|
||||
'id' => 'standard',
|
||||
],
|
||||
'filter' => [
|
||||
'id' => 'string',
|
||||
],
|
||||
];
|
||||
|
||||
$data['redirect_404']['langcode'] = [
|
||||
'title' => t('Language'),
|
||||
'help' => t('The language of this request.'),
|
||||
'field' => [
|
||||
'id' => 'redirect_404_langcode',
|
||||
],
|
||||
'filter' => [
|
||||
'id' => 'language',
|
||||
],
|
||||
];
|
||||
|
||||
$data['redirect_404']['count'] = [
|
||||
'title' => t('Count'),
|
||||
'help' => t('The number of requests with that path and language.'),
|
||||
'field' => [
|
||||
'id' => 'numeric',
|
||||
'click sortable' => TRUE,
|
||||
],
|
||||
'filter' => [
|
||||
'id' => 'numeric',
|
||||
],
|
||||
];
|
||||
|
||||
$data['redirect_404']['timestamp'] = [
|
||||
'title' => t('Timestamp'),
|
||||
'help' => t('The timestamp of the last request with that path and language.'),
|
||||
'field' => [
|
||||
'id' => 'date',
|
||||
'click sortable' => TRUE,
|
||||
],
|
||||
'filter' => [
|
||||
'id' => 'date',
|
||||
],
|
||||
];
|
||||
|
||||
$data['redirect_404']['resolved'] = [
|
||||
'title' => t('Resolved'),
|
||||
'help' => t('Whether or not this path has a redirect assigned.'),
|
||||
'field' => [
|
||||
'id' => 'boolean',
|
||||
],
|
||||
'filter' => [
|
||||
'id' => 'boolean',
|
||||
'label' => t('Resolved'),
|
||||
'use_equal' => TRUE,
|
||||
],
|
||||
];
|
||||
|
||||
$data['redirect_404']['redirect_404_operations'] = [
|
||||
'title' => t('Operations'),
|
||||
'help' => t('Provide operation buttons to handle the 404 path.'),
|
||||
'field' => [
|
||||
'id' => 'redirect_404_operations',
|
||||
'additional fields' => ['path', 'langcode'],
|
||||
'real field' => 'path',
|
||||
],
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_404\Controller;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\redirect_404\RedirectNotFoundStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Controller to ignore a path from the 'Fix 404 pages' page.
|
||||
*/
|
||||
class Fix404IgnoreController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* The redirect storage.
|
||||
*
|
||||
* @var \Drupal\redirect_404\RedirectNotFoundStorageInterface
|
||||
*/
|
||||
protected $redirectStorage;
|
||||
|
||||
/**
|
||||
* Constructs a Fix404Ignore object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory.
|
||||
* @param \Drupal\redirect_404\RedirectNotFoundStorageInterface $redirect_storage
|
||||
* A redirect storage.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, RedirectNotFoundStorageInterface $redirect_storage) {
|
||||
$this->configuration = $config_factory;
|
||||
$this->redirectStorage = $redirect_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('config.factory'),
|
||||
$container->get('redirect.not_found_storage')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds path into the ignored list.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The HttpRequest object representing the current request.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
public function ignorePath(Request $request) {
|
||||
$ignored_paths = $this->config('redirect_404.settings')->get('pages');
|
||||
$path = $request->query->get('path');
|
||||
$langcode = $request->query->get('langcode');
|
||||
|
||||
if (empty($ignored_paths) || !strpos($path, $ignored_paths)) {
|
||||
$this->redirectStorage->resolveLogRequest($path, $langcode);
|
||||
|
||||
drupal_set_message($this->t('Resolved the path %path in the database. Please check the ignored list and save the settings.', [
|
||||
'%path' => $path,
|
||||
]));
|
||||
}
|
||||
|
||||
$options = [
|
||||
'query' => [
|
||||
'ignore' => $path,
|
||||
'destination' => Url::fromRoute('redirect_404.fix_404')->getInternalPath(),
|
||||
],
|
||||
];
|
||||
|
||||
return $this->redirect('redirect.settings', [], $options);
|
||||
}
|
||||
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_404\EventSubscriber;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Path\PathMatcherInterface;
|
||||
use Drupal\redirect_404\RedirectNotFoundStorageInterface;
|
||||
use Drupal\Core\Path\CurrentPathStack;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* An EventSubscriber that listens to redirect 404 errors.
|
||||
*/
|
||||
class Redirect404Subscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The current path.
|
||||
*
|
||||
* @var \Drupal\Core\Path\CurrentPathStack
|
||||
*/
|
||||
protected $currentPath;
|
||||
|
||||
/**
|
||||
* The path matcher.
|
||||
*
|
||||
* @var \Drupal\Core\Path\PathMatcherInterface
|
||||
*/
|
||||
protected $pathMatcher;
|
||||
|
||||
/**
|
||||
* The request stack (get the URL argument(s) and combined it with the path).
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The redirect storage.
|
||||
*
|
||||
* @var \Drupal\redirect_404\RedirectNotFoundStorageInterface
|
||||
*/
|
||||
protected $redirectStorage;
|
||||
|
||||
/**
|
||||
* The configuration factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Constructs a new Redirect404Subscriber.
|
||||
*
|
||||
* @param \Drupal\Core\Path\CurrentPathStack $current_path
|
||||
* The current path.
|
||||
* @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
|
||||
* The path matcher service.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\redirect_404\RedirectNotFoundStorageInterface $redirect_storage
|
||||
* A redirect storage.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config
|
||||
* The configuration factory.
|
||||
*/
|
||||
public function __construct(CurrentPathStack $current_path, PathMatcherInterface $path_matcher, RequestStack $request_stack, LanguageManagerInterface $language_manager, RedirectNotFoundStorageInterface $redirect_storage, ConfigFactoryInterface $config) {
|
||||
$this->currentPath = $current_path;
|
||||
$this->pathMatcher = $path_matcher;
|
||||
$this->requestStack = $request_stack;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->redirectStorage = $redirect_storage;
|
||||
$this->config = $config->get('redirect_404.settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[KernelEvents::EXCEPTION][] = 'onKernelException';
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an exception of 404 Redirect errors.
|
||||
*
|
||||
* @param GetResponseForExceptionEvent $event
|
||||
* Is given by the event dispatcher.
|
||||
*/
|
||||
public function onKernelException(GetResponseForExceptionEvent $event) {
|
||||
// Only log page not found (404) errors.
|
||||
if ($event->getException() instanceof NotFoundHttpException) {
|
||||
$path = $this->currentPath->getPath();
|
||||
|
||||
// Ignore paths specified in the redirect settings.
|
||||
if ($pages = Unicode::strtolower($this->config->get('pages'))) {
|
||||
// Do not trim a trailing slash if that is the complete path.
|
||||
$path_to_match = $path === '/' ? $path : rtrim($path, '/');
|
||||
|
||||
if ($this->pathMatcher->matchPath(Unicode::strtolower($path_to_match), $pages)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow to store paths with arguments.
|
||||
if ($query_string = $this->requestStack->getCurrentRequest()->getQueryString()) {
|
||||
$query_string = '?' . $query_string;
|
||||
}
|
||||
$path .= $query_string;
|
||||
$langcode = $this->languageManager->getCurrentLanguage()->getId();
|
||||
|
||||
// Write record.
|
||||
$this->redirectStorage->logRequest($path, $langcode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_404\Form;
|
||||
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\redirect_404\SqlRedirectNotFoundStorage;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a form that lists all 404 error paths and no redirect assigned yet.
|
||||
*
|
||||
* This is a fallback for the provided default view.
|
||||
*/
|
||||
class RedirectFix404Form extends FormBase {
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The redirect storage.
|
||||
*
|
||||
* @var \Drupal\redirect_404\SqlRedirectNotFoundStorage
|
||||
*/
|
||||
protected $redirectStorage;
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatterInterface
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a RedirectFix404Form.
|
||||
*
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\redirect_404\SqlRedirectNotFoundStorage $redirect_storage
|
||||
* The redirect storage.
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date Formatter service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity manager.
|
||||
*/
|
||||
public function __construct(LanguageManagerInterface $language_manager, SqlRedirectNotFoundStorage $redirect_storage, DateFormatterInterface $date_formatter, EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->languageManager = $language_manager;
|
||||
$this->redirectStorage = $redirect_storage;
|
||||
$this->dateFormatter = $date_formatter;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('language_manager'),
|
||||
$container->get('redirect.not_found_storage'),
|
||||
$container->get('date.formatter'),
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'redirect_fix_404_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$destination = $this->getDestinationArray();
|
||||
|
||||
$search = $this->getRequest()->get('search');
|
||||
$form['#attributes'] = ['class' => ['search-form']];
|
||||
|
||||
$form['basic'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Filter 404s'),
|
||||
'#attributes' => ['class' => ['container-inline']],
|
||||
];
|
||||
$form['basic']['filter'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => '',
|
||||
'#default_value' => $search,
|
||||
'#maxlength' => 128,
|
||||
'#size' => 25,
|
||||
];
|
||||
$form['basic']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Filter'),
|
||||
'#action' => 'filter',
|
||||
];
|
||||
if ($search) {
|
||||
$form['basic']['reset'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Reset'),
|
||||
'#action' => 'reset',
|
||||
];
|
||||
}
|
||||
|
||||
$languages = $this->languageManager->getLanguages(LanguageInterface::STATE_ALL);
|
||||
$multilingual = $this->languageManager->isMultilingual();
|
||||
|
||||
$header = [
|
||||
['data' => $this->t('Path'), 'field' => 'source'],
|
||||
['data' => $this->t('Count'), 'field' => 'count', 'sort' => 'desc'],
|
||||
['data' => $this->t('Last accessed'), 'field' => 'timestamp'],
|
||||
];
|
||||
if ($multilingual) {
|
||||
$header[] = ['data' => $this->t('Language'), 'field' => 'language'];
|
||||
}
|
||||
$header[] = ['data' => $this->t('Operations')];
|
||||
|
||||
$rows = [];
|
||||
$results = $this->redirectStorage->listRequests($header, $search);
|
||||
foreach ($results as $result) {
|
||||
$path = ltrim($result->path, '/');
|
||||
|
||||
$row = [];
|
||||
$row['source'] = $path;
|
||||
$row['count'] = $result->count;
|
||||
$row['timestamp'] = $this->dateFormatter->format($result->timestamp, 'short');
|
||||
if ($multilingual) {
|
||||
if (isset($languages[$result->langcode])) {
|
||||
$row['language'] = $languages[$result->langcode]->getName();
|
||||
}
|
||||
else {
|
||||
$row['language'] = $this->t('Undefined @langcode', ['@langcode' => $result->langcode]);
|
||||
}
|
||||
}
|
||||
|
||||
$operations = [];
|
||||
if ($this->entityTypeManager->getAccessControlHandler('redirect')->createAccess()) {
|
||||
$operations['add'] = [
|
||||
'title' => $this->t('Add redirect'),
|
||||
'url' => Url::fromRoute('redirect.add', [], ['query' => ['source' => $path, 'language' => $result->langcode] + $destination]),
|
||||
];
|
||||
}
|
||||
$row['operations'] = [
|
||||
'data' => [
|
||||
'#type' => 'operations',
|
||||
'#links' => $operations,
|
||||
],
|
||||
];
|
||||
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
$form['redirect_404_table'] = [
|
||||
'#theme' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $rows,
|
||||
'#empty' => $this->t('There are no 404 errors to fix.'),
|
||||
];
|
||||
$form['redirect_404_pager'] = ['#type' => 'pager'];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
if ($form_state->getTriggeringElement()['#action'] == 'filter') {
|
||||
$form_state->setRedirect('redirect_404.fix_404', [], ['query' => ['search' => trim($form_state->getValue('filter'))]]);
|
||||
}
|
||||
else {
|
||||
$form_state->setRedirect('redirect_404.fix_404');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_404\Plugin\views\field;
|
||||
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\views\Plugin\views\field\LanguageField;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a views field for the 404 error language.
|
||||
*
|
||||
* @ingroup views_field_handlers
|
||||
*
|
||||
* @ViewsField("redirect_404_langcode")
|
||||
*/
|
||||
class Language extends LanguageField {
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* Constructs a Language object.
|
||||
*
|
||||
* @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\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, LanguageManagerInterface $language_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->languageManager = $language_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('language_manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access(AccountInterface $account) {
|
||||
return $this->languageManager->isMultilingual();
|
||||
}
|
||||
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_404\Plugin\views\field;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\views\Plugin\views\field\FieldPluginBase;
|
||||
use Drupal\views\ResultRow;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a views field for the redirect operation buttons.
|
||||
*
|
||||
* @ingroup views_field_handlers
|
||||
*
|
||||
* @ViewsField("redirect_404_operations")
|
||||
*/
|
||||
class Redirect404Operations extends FieldPluginBase {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructor for the redirect operations view field.
|
||||
*
|
||||
* @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\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, AccountInterface $current_user) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->renderer = $renderer;
|
||||
$this->currentUser = $current_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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('renderer'),
|
||||
$container->get('current_user')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clickSortable() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render(ResultRow $values) {
|
||||
$links = [];
|
||||
|
||||
$query = [
|
||||
'query' => [
|
||||
'source' => ltrim($this->getValue($values, 'path'), '/'),
|
||||
'language' => $this->getValue($values, 'langcode'),
|
||||
'destination' => $this->view->getPath(),
|
||||
],
|
||||
];
|
||||
$links['add'] = [
|
||||
'title' => $this->t('Add redirect'),
|
||||
'url' => Url::fromRoute('redirect.add', [], $query),
|
||||
];
|
||||
|
||||
if ($this->currentUser->hasPermission('administer redirect settings')) {
|
||||
$links['ignore'] = [
|
||||
'title' => $this->t('Ignore'),
|
||||
'url' => Url::fromRoute('redirect_404.ignore_404', [
|
||||
'path' => $this->getValue($values, 'path'),
|
||||
'langcode' => $this->getValue($values, 'langcode'),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
$operations['data'] = [
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
];
|
||||
|
||||
return $this->renderer->render($operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access(AccountInterface $account) {
|
||||
return $this->entityTypeManager->getAccessControlHandler('redirect')->createAccess();
|
||||
}
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_404;
|
||||
|
||||
/**
|
||||
* Interface for redirect 404 services.
|
||||
*/
|
||||
interface RedirectNotFoundStorageInterface {
|
||||
|
||||
/**
|
||||
* Merges a 404 request log in the database.
|
||||
*
|
||||
* @param string $path
|
||||
* The path of the current request.
|
||||
* @param string $langcode
|
||||
* The ID of the language code.
|
||||
*/
|
||||
public function logRequest($path, $langcode);
|
||||
|
||||
/**
|
||||
* Marks a 404 request log as resolved.
|
||||
*
|
||||
* @param string $path
|
||||
* The path of the current request.
|
||||
* @param string $langcode
|
||||
* The ID of the language code.
|
||||
*/
|
||||
public function resolveLogRequest($path, $langcode);
|
||||
|
||||
/**
|
||||
* Returns the 404 request data.
|
||||
*
|
||||
* @param array $header
|
||||
* An array containing arrays of the redirect_404 fields data.
|
||||
* @param string $search
|
||||
* The search text. It is possible to have multiple '*' as a wildcard.
|
||||
*
|
||||
* @return array
|
||||
* A list of objects with the properties:
|
||||
* - path
|
||||
* - count
|
||||
* - timestamp
|
||||
* - langcode
|
||||
* - resolved
|
||||
*/
|
||||
public function listRequests(array $header = [], $search = NULL);
|
||||
|
||||
/**
|
||||
* Cleans the irrelevant 404 request logs.
|
||||
*/
|
||||
public function purgeOldRequests();
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_404\Render;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
|
||||
use Drupal\Core\Logger\LoggerChannelFactory;
|
||||
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* Allows 'page not found' events to be suppressed by returning a NullLogger.
|
||||
*/
|
||||
class Redirect404LogSuppressor implements LoggerChannelFactoryInterface {
|
||||
use DependencySerializationTrait;
|
||||
|
||||
/**
|
||||
* The logger channel factory.
|
||||
*
|
||||
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
|
||||
*/
|
||||
protected $loggerChannelFactory;
|
||||
|
||||
/**
|
||||
* The configuration factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* Constructs a Redirect404LogSuppressor object.
|
||||
*
|
||||
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_channel_factory
|
||||
* The logger channel factory.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
*/
|
||||
public function __construct(LoggerChannelFactoryInterface $logger_channel_factory, ConfigFactoryInterface $config_factory) {
|
||||
$this->loggerChannelFactory = $logger_channel_factory;
|
||||
$this->configFactory = $config_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($channel) {
|
||||
if ($channel == 'page not found' && $this->configFactory->get('redirect_404.settings')->get('suppress_404')) {
|
||||
// Do not log if a 404 error is detected and the suppress_404 is enabled.
|
||||
return new NullLogger();
|
||||
}
|
||||
|
||||
// Call LoggerChannelFactory to let the default logger workflow proceed.
|
||||
return $this->loggerChannelFactory->get($channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addLogger(LoggerInterface $logger, $priority = 0) {
|
||||
$this->loggerChannelFactory->addLogger($logger, $priority);
|
||||
}
|
||||
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_404;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Database\Connection;
|
||||
|
||||
/**
|
||||
* Provides an SQL implementation for redirect not found storage.
|
||||
*
|
||||
* To keep a limited amount of relevant records, we compute a relevancy based
|
||||
* on the amount of visits for each row, deleting the less visited record and
|
||||
* sorted by timestamp.
|
||||
*/
|
||||
class SqlRedirectNotFoundStorage implements RedirectNotFoundStorageInterface {
|
||||
|
||||
/**
|
||||
* Maximum column length for invalid paths.
|
||||
*/
|
||||
const MAX_PATH_LENGTH = 191;
|
||||
|
||||
/**
|
||||
* Active database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The configuration factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* Constructs a new SqlRedirectNotFoundStorage.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* A Database connection to use for reading and writing database data.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
*/
|
||||
public function __construct(Connection $database, ConfigFactoryInterface $config_factory) {
|
||||
$this->database = $database;
|
||||
$this->configFactory = $config_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function logRequest($path, $langcode) {
|
||||
if (Unicode::strlen($path) > static::MAX_PATH_LENGTH) {
|
||||
// Don't attempt to log paths that would result in an exception. There is
|
||||
// no point in logging truncated paths, as they cannot be used to build a
|
||||
// new redirect.
|
||||
return;
|
||||
}
|
||||
// Ignore invalid UTF-8, which can't be logged.
|
||||
if (!Unicode::validateUtf8($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the request is not new, update its count and timestamp.
|
||||
$this->database->merge('redirect_404')
|
||||
->key('path', $path)
|
||||
->key('langcode', $langcode)
|
||||
->expression('count', 'count + 1')
|
||||
->fields([
|
||||
'timestamp' => REQUEST_TIME,
|
||||
'count' => 1,
|
||||
'resolved' => 0,
|
||||
])
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resolveLogRequest($path, $langcode) {
|
||||
$this->database->update('redirect_404')
|
||||
->fields(['resolved' => 1])
|
||||
->condition('path', $path)
|
||||
->condition('langcode', $langcode)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function purgeOldRequests() {
|
||||
$row_limit = $this->configFactory->get('redirect_404.settings')->get('row_limit');
|
||||
|
||||
// In admin form 0 used as value for 'All' label.
|
||||
if ($row_limit == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query = $this->database->select('redirect_404', 'r404');
|
||||
$query->fields('r404', ['timestamp']);
|
||||
// On databases known to support log(), use it to calculate a logarithmic
|
||||
// scale of the count, to delete records with count of 1-9 first, then
|
||||
// 10-99 and so on.
|
||||
if ($this->database->driver() == 'mysql' || $this->database->driver() == 'pgsql') {
|
||||
$query->addExpression('floor(log(10, count))', 'count_log');
|
||||
$query->orderBy('count_log', 'DESC');
|
||||
}
|
||||
$query->orderBy('timestamp', 'DESC');
|
||||
$cutoff = $query
|
||||
->range($row_limit, 1)
|
||||
->execute()
|
||||
->fetchAssoc();
|
||||
|
||||
if (!empty($cutoff)) {
|
||||
// Delete records having older timestamp and less visits (on a logarithmic
|
||||
// scale) than cutoff.
|
||||
$delete_query = $this->database->delete('redirect_404');
|
||||
|
||||
if ($this->database->driver() == 'mysql' || $this->database->driver() == 'pgsql') {
|
||||
// Delete rows with same count_log AND older timestamp than cutoff.
|
||||
$and_condition = $delete_query->andConditionGroup()
|
||||
->where('floor(log(10, count)) = :count_log2', [':count_log2' => $cutoff['count_log']])
|
||||
->condition('timestamp', $cutoff['timestamp'], '<=');
|
||||
|
||||
// And delete all the rows with count_log less than the cutoff.
|
||||
$condition = $delete_query->orConditionGroup()
|
||||
->where('floor(log(10, count)) < :count_log1', [':count_log1' => $cutoff['count_log']])
|
||||
->condition($and_condition);
|
||||
$delete_query->condition($condition);
|
||||
}
|
||||
else {
|
||||
$delete_query->condition('timestamp', $cutoff['timestamp'], '<=');
|
||||
}
|
||||
$delete_query->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listRequests(array $header = [], $search = NULL) {
|
||||
$query = $this->database
|
||||
->select('redirect_404', 'r404')
|
||||
->extend('Drupal\Core\Database\Query\TableSortExtender')
|
||||
->orderByHeader($header)
|
||||
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
|
||||
->limit(25)
|
||||
->fields('r404');
|
||||
|
||||
if ($search) {
|
||||
// Replace wildcards with PDO wildcards.
|
||||
// @todo Find a way to write a nicer pattern.
|
||||
$wildcard = '%' . trim(preg_replace('!\*+!', '%', $this->database->escapeLike($search)), '%') . '%';
|
||||
$query->condition('path', $wildcard, 'LIKE');
|
||||
}
|
||||
$results = $query->condition('resolved', 0, '=')->execute()->fetchAll();
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_404\Tests;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\redirect\Tests\AssertRedirectTrait;
|
||||
|
||||
/**
|
||||
* UI tests for redirect_404 module with language and content translation.
|
||||
*
|
||||
* This runs the exact same tests as Fix404RedirectUITest, but with both
|
||||
* language and content translation modules enabled.
|
||||
*
|
||||
* @group redirect_404
|
||||
*/
|
||||
class Fix404RedirectUILanguageTest extends Redirect404TestBase {
|
||||
|
||||
use AssertRedirectTrait;
|
||||
|
||||
/**
|
||||
* Additional modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['language'];
|
||||
|
||||
/**
|
||||
* Admin user's permissions for this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $adminPermissions = [
|
||||
'administer redirects',
|
||||
'administer redirect settings',
|
||||
'access content',
|
||||
'bypass node access',
|
||||
'create url aliases',
|
||||
'administer url aliases',
|
||||
'administer languages',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Enable some languages for this test.
|
||||
$language = ConfigurableLanguage::createFromLangcode('de');
|
||||
$language->save();
|
||||
$language = ConfigurableLanguage::createFromLangcode('es');
|
||||
$language->save();
|
||||
$language = ConfigurableLanguage::createFromLangcode('fr');
|
||||
$language->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the fix 404 pages workflow with language and content translation.
|
||||
*/
|
||||
public function testFix404RedirectList() {
|
||||
// Visit a non existing page to have the 404 redirect_error entry.
|
||||
$this->drupalGet('fr/testing');
|
||||
|
||||
$redirect = \Drupal::database()->select('redirect_404')
|
||||
->fields('redirect_404')
|
||||
->condition('path', '/testing')
|
||||
->execute()
|
||||
->fetchAll();
|
||||
if (count($redirect) == 0) {
|
||||
$this->fail('No record was added');
|
||||
}
|
||||
|
||||
// Go to the "fix 404" page and check the listing.
|
||||
$this->drupalGet('admin/config/search/redirect/404');
|
||||
$this->assertText('testing');
|
||||
$this->assertLanguageInTableBody('French');
|
||||
// Check the Language view filter uses the default language filter.
|
||||
$this->assertOption('edit-langcode', 'All');
|
||||
$this->assertOption('edit-langcode', 'en');
|
||||
$this->assertOption('edit-langcode', 'de');
|
||||
$this->assertOption('edit-langcode', 'es');
|
||||
$this->assertOption('edit-langcode', 'fr');
|
||||
$this->assertOption('edit-langcode', LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
$this->clickLink(t('Add redirect'));
|
||||
|
||||
// Check if we generate correct Add redirect url and if the form is
|
||||
// pre-filled.
|
||||
$destination = Url::fromRoute('redirect_404.fix_404')->getInternalPath();
|
||||
$options = [
|
||||
'query' => [
|
||||
'source' => 'testing',
|
||||
'language' => 'fr',
|
||||
'destination' => $destination,
|
||||
]
|
||||
];
|
||||
$this->assertUrl('admin/config/search/redirect/add', $options);
|
||||
$this->assertFieldByName('redirect_source[0][path]', 'testing');
|
||||
$this->assertOptionSelected('edit-language-0-value', 'fr');
|
||||
// Save the redirect.
|
||||
$edit = ['redirect_redirect[0][uri]' => '/node'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$this->assertUrl('admin/config/search/redirect/404');
|
||||
$this->assertText('There are no 404 errors to fix.');
|
||||
// Check if the redirect works as expected.
|
||||
$this->assertRedirect('fr/testing', 'fr/node', 'HTTP/1.1 301 Moved Permanently');
|
||||
|
||||
// Test removing a redirect assignment, visit again the non existing page.
|
||||
$this->drupalGet('admin/config/search/redirect');
|
||||
$this->assertText('testing');
|
||||
$this->assertLanguageInTableBody('French');
|
||||
$this->clickLink('Delete', 0);
|
||||
$this->drupalPostForm(NULL, [], 'Delete');
|
||||
$this->assertUrl('admin/config/search/redirect');
|
||||
$this->assertText('There is no redirect yet.');
|
||||
$this->drupalGet('admin/config/search/redirect/404');
|
||||
$this->assertText('There are no 404 errors to fix.');
|
||||
// Should be listed again in the 404 overview.
|
||||
$this->drupalGet('fr/testing');
|
||||
$this->drupalGet('admin/config/search/redirect/404');
|
||||
$this->assertLanguageInTableBody('French');
|
||||
// Check the error path visit count.
|
||||
$this->assertFieldByXPath('//table/tbody/tr/td[2]', 2);
|
||||
$this->clickLink('Add redirect');
|
||||
// Save the redirect with a different langcode.
|
||||
$this->assertFieldByName('redirect_source[0][path]', 'testing');
|
||||
$this->assertOptionSelected('edit-language-0-value', 'fr');
|
||||
$edit['language[0][value]'] = 'es';
|
||||
$this->drupalPostForm(NULL, $edit, 'Save');
|
||||
$this->assertUrl('admin/config/search/redirect/404');
|
||||
// Should still be listed, redirecting to another language does not resolve
|
||||
// the path.
|
||||
$this->assertLanguageInTableBody('French');
|
||||
$this->drupalGet('admin/config/search/redirect');
|
||||
$this->assertLanguageInTableBody('Spanish');
|
||||
// Check if the redirect works as expected.
|
||||
$this->assertRedirect('es/testing', 'es/node', 'HTTP/1.1 301 Moved Permanently');
|
||||
|
||||
// Visit multiple non existing pages to test the Redirect 404 View.
|
||||
$this->drupalGet('testing1');
|
||||
$this->drupalGet('de/testing2');
|
||||
$this->drupalGet('de/testing2?test=1');
|
||||
$this->drupalGet('de/testing2?test=2');
|
||||
$this->drupalGet('admin/config/search/redirect/404');
|
||||
$this->assertLanguageInTableBody('French');
|
||||
$this->assertLanguageInTableBody('English');
|
||||
$this->assertLanguageInTableBody('German');
|
||||
$this->assertText('testing1');
|
||||
$this->assertText('testing2');
|
||||
$this->assertText('testing2?test=1');
|
||||
$this->assertText('testing2?test=2');
|
||||
|
||||
// Test the Language view filter.
|
||||
$this->drupalGet('admin/config/search/redirect/404', ['query' => ['langcode' => 'de']]);
|
||||
$this->assertText('English');
|
||||
$this->assertNoLanguageInTableBody('English');
|
||||
$this->assertLanguageInTableBody('German');
|
||||
$this->assertNoText('testing1');
|
||||
$this->assertText('testing2');
|
||||
$this->assertText('testing2?test=1');
|
||||
$this->assertText('testing2?test=2');
|
||||
$this->drupalGet('admin/config/search/redirect/404');
|
||||
$this->assertLanguageInTableBody('English');
|
||||
$this->assertLanguageInTableBody('German');
|
||||
$this->assertText('testing1');
|
||||
$this->assertText('testing2');
|
||||
$this->assertText('testing2?test=1');
|
||||
$this->assertText('testing2?test=2');
|
||||
$this->drupalGet('admin/config/search/redirect/404', ['query' => ['langcode' => 'en']]);
|
||||
$this->assertLanguageInTableBody('English');
|
||||
$this->assertNoLanguageInTableBody('German');
|
||||
$this->assertText('testing1');
|
||||
$this->assertNoText('testing2');
|
||||
$this->assertNoText('testing2?test=1');
|
||||
$this->assertNoText('testing2?test=2');
|
||||
|
||||
// Assign a redirect to 'testing1'.
|
||||
$this->clickLink('Add redirect');
|
||||
$options = [
|
||||
'query' => [
|
||||
'source' => 'testing1',
|
||||
'language' => 'en',
|
||||
'destination' => $destination,
|
||||
]
|
||||
];
|
||||
$this->assertUrl('admin/config/search/redirect/add', $options);
|
||||
$this->assertFieldByName('redirect_source[0][path]', 'testing1');
|
||||
$this->assertOptionSelected('edit-language-0-value', 'en');
|
||||
$edit = ['redirect_redirect[0][uri]' => '/node'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$this->assertUrl('admin/config/search/redirect/404');
|
||||
$this->assertNoLanguageInTableBody('English');
|
||||
$this->assertLanguageInTableBody('German');
|
||||
$this->drupalGet('admin/config/search/redirect');
|
||||
$this->assertLanguageInTableBody('Spanish');
|
||||
$this->assertLanguageInTableBody('English');
|
||||
// Check if the redirect works as expected.
|
||||
$this->assertRedirect('/testing1', '/node', 'HTTP/1.1 301 Moved Permanently');
|
||||
}
|
||||
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_404\Tests;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* UI tests for redirect_404 module.
|
||||
*
|
||||
* @group redirect_404
|
||||
*/
|
||||
class Fix404RedirectUITest extends Redirect404TestBase {
|
||||
|
||||
/**
|
||||
* Tests the fix 404 pages workflow.
|
||||
*/
|
||||
public function testFix404Pages() {
|
||||
// Visit a non existing page to have the 404 redirect_error entry.
|
||||
$this->drupalGet('non-existing0');
|
||||
|
||||
// Go to the "fix 404" page and check the listing.
|
||||
$this->drupalGet('admin/config/search/redirect/404');
|
||||
$this->assertText('non-existing0');
|
||||
$this->clickLink(t('Add redirect'));
|
||||
|
||||
// Check if we generate correct Add redirect url and if the form is
|
||||
// pre-filled.
|
||||
$destination = Url::fromRoute('redirect_404.fix_404')->getInternalPath();
|
||||
$options = [
|
||||
'query' => [
|
||||
'source' => 'non-existing0',
|
||||
'language' => 'en',
|
||||
'destination' => $destination,
|
||||
]
|
||||
];
|
||||
$this->assertUrl('admin/config/search/redirect/add', $options);
|
||||
$this->assertFieldByName('redirect_source[0][path]', 'non-existing0');
|
||||
// Save the redirect.
|
||||
$edit = ['redirect_redirect[0][uri]' => '/node'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$this->assertUrl('admin/config/search/redirect/404');
|
||||
$this->assertText('There are no 404 errors to fix.');
|
||||
// Check if the redirect works as expected.
|
||||
$this->drupalGet('non-existing0');
|
||||
$this->assertUrl('node');
|
||||
|
||||
// Test removing a redirect assignment, visit again the non existing page.
|
||||
$this->drupalGet('admin/config/search/redirect');
|
||||
$this->assertText('non-existing0');
|
||||
$this->clickLink('Delete', 0);
|
||||
$this->drupalPostForm(NULL, [], 'Delete');
|
||||
$this->assertUrl('admin/config/search/redirect');
|
||||
$this->assertText('There is no redirect yet.');
|
||||
$this->drupalGet('admin/config/search/redirect/404');
|
||||
$this->assertText('There are no 404 errors to fix.');
|
||||
// Should be listed again in the 404 overview.
|
||||
$this->drupalGet('non-existing0');
|
||||
$this->drupalGet('admin/config/search/redirect/404');
|
||||
$this->assertText('non-existing0');
|
||||
|
||||
// Visit multiple non existing pages to test the Redirect 404 View.
|
||||
$this->drupalGet('non-existing0?test=1');
|
||||
$this->drupalGet('non-existing0?test=2');
|
||||
$this->drupalGet('non-existing1');
|
||||
$this->drupalGet('non-existing2');
|
||||
$this->drupalGet('admin/config/search/redirect/404');
|
||||
$this->assertText('non-existing0?test=1');
|
||||
$this->assertText('non-existing0?test=2');
|
||||
$this->assertText('non-existing0');
|
||||
$this->assertText('non-existing1');
|
||||
$this->assertText('non-existing2');
|
||||
|
||||
// Test the Path view filter.
|
||||
$this->drupalGet('admin/config/search/redirect/404', ['query' => ['path' => 'test=']]);
|
||||
$this->assertText('non-existing0?test=1');
|
||||
$this->assertText('non-existing0?test=2');
|
||||
$this->assertNoText('non-existing1');
|
||||
$this->assertNoText('non-existing2');
|
||||
$this->drupalGet('admin/config/search/redirect/404', ['query' => ['path' => 'existing1']]);
|
||||
$this->assertNoText('non-existing0?test=1');
|
||||
$this->assertNoText('non-existing0?test=2');
|
||||
$this->assertNoText('non-existing0');
|
||||
$this->assertText('non-existing1');
|
||||
$this->assertNoText('non-existing2');
|
||||
$this->drupalGet('admin/config/search/redirect/404');
|
||||
$this->assertText('non-existing0?test=1');
|
||||
$this->assertText('non-existing0?test=2');
|
||||
$this->assertText('non-existing0');
|
||||
$this->assertText('non-existing1');
|
||||
$this->assertText('non-existing2');
|
||||
$this->drupalGet('admin/config/search/redirect/404', ['query' => ['path' => 'g2']]);
|
||||
$this->assertNoText('non-existing0?test=1');
|
||||
$this->assertNoText('non-existing0?test=2');
|
||||
$this->assertNoText('non-existing0');
|
||||
$this->assertNoText('non-existing1');
|
||||
$this->assertText('non-existing2');
|
||||
|
||||
// Assign a redirect to 'non-existing2'.
|
||||
$this->clickLink('Add redirect');
|
||||
$options = [
|
||||
'query' => [
|
||||
'source' => 'non-existing2',
|
||||
'language' => 'en',
|
||||
'destination' => $destination,
|
||||
]
|
||||
];
|
||||
$this->assertUrl('admin/config/search/redirect/add', $options);
|
||||
$this->assertFieldByName('redirect_source[0][path]', 'non-existing2');
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$this->assertUrl('admin/config/search/redirect/404');
|
||||
$this->assertText('non-existing0?test=1');
|
||||
$this->assertText('non-existing0?test=2');
|
||||
$this->assertText('non-existing0');
|
||||
$this->assertText('non-existing1');
|
||||
$this->assertNoText('non-existing2');
|
||||
// Check if the redirect works as expected.
|
||||
$this->drupalGet('admin/config/search/redirect');
|
||||
$this->assertText('non-existing2');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the redirect ignore pages.
|
||||
*/
|
||||
public function testIgnorePages() {
|
||||
// Create two nodes.
|
||||
$node1 = $this->drupalCreateNode(['type' => 'page']);
|
||||
$node2 = $this->drupalCreateNode(['type' => 'page']);
|
||||
|
||||
// Set some pages to be ignored just for the test.
|
||||
$node_to_ignore = '/node/' . $node1->id() . '/test';
|
||||
$terms_to_ignore = '/term/*';
|
||||
$pages = $node_to_ignore . "\r\n" . $terms_to_ignore;
|
||||
\Drupal::configFactory()
|
||||
->getEditable('redirect_404.settings')
|
||||
->set('pages', $pages)
|
||||
->save();
|
||||
|
||||
// Visit ignored or non existing pages.
|
||||
$this->drupalGet('node/' . $node1->id() . '/test');
|
||||
$this->drupalGet('term/foo');
|
||||
$this->drupalGet('term/1');
|
||||
// Go to the "fix 404" page and check there are no 404 entries.
|
||||
$this->drupalGet('admin/config/search/redirect/404');
|
||||
$this->assertNoText('node/' . $node1->id() . '/test');
|
||||
$this->assertNoText('term/foo');
|
||||
$this->assertNoText('term/1');
|
||||
|
||||
// Visit non existing but 'unignored' page.
|
||||
$this->drupalGet('node/' . $node2->id() . '/test');
|
||||
// Go to the "fix 404" page and check there is a 404 entry.
|
||||
$this->drupalGet('admin/config/search/redirect/404');
|
||||
$this->assertText('node/' . $node2->id() . '/test');
|
||||
|
||||
// Add this 404 entry to the 'ignore path' list, assert it works properly.
|
||||
$path_to_ignore = '/node/' . $node2->id() . '/test';
|
||||
$destination = '&destination=admin/config/search/redirect/404';
|
||||
$this->clickLink('Ignore');
|
||||
$this->assertUrl('admin/config/search/redirect/settings?ignore=' . $path_to_ignore . $destination);
|
||||
$this->assertText('Resolved the path ' . $path_to_ignore . ' in the database. Please check the ignored list and save the settings.');
|
||||
$xpath = $this->xpath('//*[@id="edit-ignore-pages"]')[0]->asXML();
|
||||
$this->assertTrue(strpos($xpath, $node_to_ignore), $node_to_ignore . " in 'Path to ignore' found");
|
||||
$this->assertTrue(strpos($xpath, $terms_to_ignore), $terms_to_ignore . " in 'Path to ignore' found");
|
||||
$this->assertTrue(strpos($xpath, $path_to_ignore), $path_to_ignore . " in 'Path to ignore' found");
|
||||
|
||||
// Save the path with wildcard, but omitting the leading slash.
|
||||
$nodes_to_ignore = 'node/*';
|
||||
$edit = ['ignore_pages' => $nodes_to_ignore . "\r\n" . $terms_to_ignore];
|
||||
$this->drupalPostForm(NULL, $edit, 'Save configuration');
|
||||
// Should redirect to 'Fix 404'. Check the 404 entry is not shown anymore.
|
||||
$this->assertUrl('admin/config/search/redirect/404');
|
||||
$this->assertText('Configuration was saved.');
|
||||
$this->assertNoText('node/' . $node2->id() . '/test');
|
||||
$this->assertText('There are no 404 errors to fix.');
|
||||
|
||||
// Go back to the settings to check the 'Path to ignore' configurations.
|
||||
$this->drupalGet('admin/config/search/redirect/settings');
|
||||
$xpath = $this->xpath('//*[@id="edit-ignore-pages"]')[0]->asXML();
|
||||
// Check that the new page to ignore has been saved with leading slash.
|
||||
$this->assertTrue(strpos($xpath, '/' . $nodes_to_ignore), '/' . $nodes_to_ignore . " in 'Path to ignore' found");
|
||||
$this->assertTrue(strpos($xpath, $terms_to_ignore), $terms_to_ignore . " in 'Path to ignore' found");
|
||||
$this->assertFalse(strpos($xpath, $node_to_ignore), $node_to_ignore . " in 'Path to ignore' found");
|
||||
$this->assertFalse(strpos($xpath, $path_to_ignore), $path_to_ignore . " in 'Path to ignore' found");
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_404\Tests;
|
||||
|
||||
/**
|
||||
* Tests suppressing 404 logs if the suppress_404 setting is enabled.
|
||||
*
|
||||
* @group redirect_404
|
||||
*/
|
||||
class Redirect404LogSuppressorTest extends Redirect404TestBase {
|
||||
|
||||
/**
|
||||
* Additional modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['dblog'];
|
||||
|
||||
/**
|
||||
* A user with some relevant administrative permissions.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* A user without any permissions.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $webUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create users with specific permissions.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'administer redirect settings',
|
||||
'administer redirects',
|
||||
]);
|
||||
$this->webUser = $this->drupalCreateUser([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the suppress_404 service.
|
||||
*/
|
||||
public function testSuppress404Events() {
|
||||
// Cause a page not found and an access denied event.
|
||||
$this->drupalGet('page-not-found');
|
||||
$this->assertResponse(404);
|
||||
$this->drupalLogin($this->webUser);
|
||||
$this->drupalGet('admin/reports/dblog');
|
||||
$this->assertResponse(403);
|
||||
|
||||
// Assert the events are logged in the dblog reports.
|
||||
$this->assertEqual(db_query("SELECT COUNT(*) FROM {watchdog} WHERE type = 'page not found'")->fetchField(), 1);
|
||||
$this->assertEqual(db_query("SELECT COUNT(*) FROM {watchdog} WHERE type = 'access denied'")->fetchField(), 1);
|
||||
|
||||
// Login as admin and enable suppress_404 to avoid logging the 404 event.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$edit = ['suppress_404' => TRUE];
|
||||
$this->drupalPostForm('admin/config/search/redirect/settings', $edit, 'Save configuration');
|
||||
|
||||
// Cause again a page not found and an access denied event.
|
||||
$this->drupalGet('page-not-found');
|
||||
$this->assertResponse(404);
|
||||
$this->drupalLogin($this->webUser);
|
||||
$this->drupalGet('admin/reports/dblog');
|
||||
$this->assertResponse(403);
|
||||
|
||||
// Assert only the new access denied event is logged now.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->assertEqual(db_query("SELECT COUNT(*) FROM {watchdog} WHERE type = 'page not found'")->fetchField(), 1);
|
||||
$this->assertEqual(db_query("SELECT COUNT(*) FROM {watchdog} WHERE type = 'access denied'")->fetchField(), 2);
|
||||
|
||||
}
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_404\Tests;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* This class provides methods specifically for testing redirect 404 paths.
|
||||
*/
|
||||
abstract class Redirect404TestBase extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'redirect_404',
|
||||
'node',
|
||||
'path',
|
||||
];
|
||||
|
||||
/**
|
||||
* Permissions for the admin user.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $adminPermissions = [
|
||||
'administer redirects',
|
||||
'administer redirect settings',
|
||||
'access content',
|
||||
'bypass node access',
|
||||
'create url aliases',
|
||||
'administer url aliases',
|
||||
];
|
||||
|
||||
/**
|
||||
* A user with administrative permissions.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create an admin user.
|
||||
$this->adminUser = $this->drupalCreateUser($this->adminPermissions);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Page']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes if the language of the 404 path IS found on the loaded page.
|
||||
*
|
||||
* Because assertText() checks also in the Language select options, this
|
||||
* specific assertion in the redirect 404 table body is needed.
|
||||
*
|
||||
* @param string $language
|
||||
* The language to assert in the redirect 404 table body.
|
||||
* @param string $body
|
||||
* (optional) The table body xpath where to assert the language. Defaults
|
||||
* to '//table/tbody'.
|
||||
* @param string $message
|
||||
* (optional) A message to display with the assertion. Do not translate
|
||||
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
|
||||
* variables in the message text, not t(). If left blank, a default message
|
||||
* will be displayed.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on pass, FALSE on fail.
|
||||
*/
|
||||
protected function assertLanguageInTableBody($language, $body = '//table/tbody', $message = '') {
|
||||
return $this->assertLanguageInTableBodyHelper($language, $body, $message, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes if the language of the 404 path is NOT found on the loaded page.
|
||||
*
|
||||
* Because assertText() checks also in the Language select options, this
|
||||
* specific assertion in the redirect 404 table body is needed.
|
||||
*
|
||||
* @param string $language
|
||||
* The language to assert in the redirect 404 table body.
|
||||
* @param string $body
|
||||
* (optional) The table body xpath where to assert the language. Defaults
|
||||
* to '//table/tbody'.
|
||||
* @param string $message
|
||||
* (optional) A message to display with the assertion. Do not translate
|
||||
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
|
||||
* variables in the message text, not t(). If left blank, a default message
|
||||
* will be displayed.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on pass, FALSE on fail.
|
||||
*/
|
||||
protected function assertNoLanguageInTableBody($language, $body = '//table/tbody', $message = '') {
|
||||
return $this->assertLanguageInTableBodyHelper($language, $body, $message, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for assertLanguageInTableBody and assertNoLanguageInTableBody.
|
||||
*
|
||||
* @param array $language
|
||||
* The language to assert in the redirect 404 table body.
|
||||
* @param string $body
|
||||
* (optional) The table body xpath where to assert the language. Defaults
|
||||
* to '//table/tbody'.
|
||||
* @param string $message
|
||||
* (optional) A message to display with the assertion. Do not translate
|
||||
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
|
||||
* variables in the message text, not t(). If left blank, a default message
|
||||
* will be displayed.
|
||||
* @param bool $not_exists
|
||||
* (optional) TRUE if this language should not exist, FALSE if it should.
|
||||
* Defaults to TRUE.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on pass, FALSE on fail.
|
||||
*/
|
||||
protected function assertLanguageInTableBodyHelper($language, $body = '//table/tbody', $message = '', $not_exists = TRUE) {
|
||||
if (!$message) {
|
||||
if (!$not_exists) {
|
||||
$message = new FormattableMarkup('Language "@language" found in 404 table.', ['@language' => $language]);
|
||||
}
|
||||
else {
|
||||
$message = new FormattableMarkup('Language "@language" not found in 404 table.', ['@language' => $language]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($not_exists) {
|
||||
return $this->assertFalse(strpos($this->xpath($body)[0]->asXML(), $language), $message);
|
||||
}
|
||||
else {
|
||||
return $this->assertTrue(strpos($this->xpath($body)[0]->asXML(), $language), $message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\redirect_404\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the clean up cron job for redirect_404.
|
||||
*
|
||||
* @group redirect_404
|
||||
*/
|
||||
class Fix404RedirectCronJobTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['redirect_404'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installSchema('redirect_404', 'redirect_404');
|
||||
|
||||
// Insert some records in the test table with a given count and timestamp.
|
||||
$this->insert404Row('/test1', 12, strtotime('now'));
|
||||
$this->insert404Row('/test2', 5, strtotime('-1 hour'));
|
||||
$this->insert404Row('/test3', 315, strtotime('-1 week'));
|
||||
$this->insert404Row('/test4', 300, strtotime('-1 month'));
|
||||
$this->insert404Row('/test5', 1557, strtotime('-1 week'));
|
||||
$this->insert404Row('/test6', 1, strtotime('-1 day'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding and deleting rows from redirect_404 table.
|
||||
*/
|
||||
function testRedirect404CronJob() {
|
||||
// Set the limit to 3 just for the test.
|
||||
\Drupal::configFactory()
|
||||
->getEditable('redirect_404.settings')
|
||||
->set('row_limit', 3)
|
||||
->save();
|
||||
|
||||
// Check that there are 6 rows in the redirect_404 table.
|
||||
$result = db_query("SELECT COUNT(*) FROM {redirect_404}")->fetchField();
|
||||
$this->assertEquals(6, $result);
|
||||
|
||||
// Run cron to drop 3 rows from the redirect_404 test table.
|
||||
redirect_404_cron();
|
||||
|
||||
$result = db_query("SELECT COUNT(*) FROM {redirect_404}")->fetchField();
|
||||
$this->assertEquals(3, $result);
|
||||
|
||||
// Check there are only 3 rows with more count in the redirect_404 table.
|
||||
if (\Drupal::database()->driver() == 'mysql' || \Drupal::database()->driver() == 'pgsql') {
|
||||
$this->assertNo404Row('/test1');
|
||||
$this->assertNo404Row('/test2');
|
||||
$this->assert404Row('/test3');
|
||||
$this->assert404Row('/test4');
|
||||
$this->assert404Row('/test5');
|
||||
$this->assertNo404Row('/test6');
|
||||
}
|
||||
else {
|
||||
// In SQLite is the opposite: the 3 rows kept are the newest ones.
|
||||
$this->assert404Row('/test1');
|
||||
$this->assert404Row('/test2');
|
||||
$this->assertNo404Row('/test3');
|
||||
$this->assertNo404Row('/test4');
|
||||
$this->assertNo404Row('/test5');
|
||||
$this->assert404Row('/test6');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding rows and deleting one row from redirect_404 table.
|
||||
*/
|
||||
function testRedirect404CronJobKeepAllButOne() {
|
||||
// Set the limit to 5 just for the test.
|
||||
\Drupal::configFactory()
|
||||
->getEditable('redirect_404.settings')
|
||||
->set('row_limit', 5)
|
||||
->save();
|
||||
|
||||
// Check that there are 6 rows in the redirect_404 table.
|
||||
$result = db_query("SELECT COUNT(*) FROM {redirect_404}")->fetchField();
|
||||
$this->assertEquals(6, $result);
|
||||
|
||||
// Run cron to drop just 1 row from the redirect_404 test table.
|
||||
redirect_404_cron();
|
||||
|
||||
$result = db_query("SELECT COUNT(*) FROM {redirect_404}")->fetchField();
|
||||
$this->assertEquals(5, $result);
|
||||
|
||||
// Check only the row with least count has been removed from the table.
|
||||
if (\Drupal::database()->driver() == 'mysql' || \Drupal::database()->driver() == 'pgsql') {
|
||||
$this->assert404Row('/test1');
|
||||
$this->assert404Row('/test2');
|
||||
$this->assert404Row('/test3');
|
||||
$this->assert404Row('/test4');
|
||||
$this->assert404Row('/test5');
|
||||
$this->assertNo404Row('/test6');
|
||||
}
|
||||
else {
|
||||
// In SQlite, only the oldest row is deleted.
|
||||
$this->assert404Row('/test1');
|
||||
$this->assert404Row('/test2');
|
||||
$this->assert404Row('/test3');
|
||||
$this->assertNo404Row('/test4');
|
||||
$this->assert404Row('/test5');
|
||||
$this->assert404Row('/test6');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a 404 request log in the redirect_404 test table.
|
||||
*
|
||||
* @param string $path
|
||||
* The path of the request.
|
||||
* @param int $count
|
||||
* (optional) The visits count of the request.
|
||||
* @param int $timestamp
|
||||
* (optional) The timestamp of the last visited request.
|
||||
* @param string $langcode
|
||||
* (optional) The langcode of the request.
|
||||
*/
|
||||
protected function insert404Row($path, $count = 1, $timestamp = 0, $langcode = 'en') {
|
||||
db_insert('redirect_404')
|
||||
->fields([
|
||||
'path' => $path,
|
||||
'langcode' => $langcode,
|
||||
'count' => $count,
|
||||
'timestamp' => $timestamp,
|
||||
'resolved' => 0,
|
||||
])
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes if the row with the given parameters is in the redirect_404 table.
|
||||
*
|
||||
* @param string $path
|
||||
* The path of the request.
|
||||
* @param string $langcode
|
||||
* (optional) The langcode of the request.
|
||||
*/
|
||||
protected function assert404Row($path, $langcode = 'en') {
|
||||
$this->assert404RowHelper($path, $langcode, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes if the row with the given parameters is NOT in the redirect_404 table.
|
||||
*
|
||||
* @param string $path
|
||||
* The path of the request.
|
||||
* @param string $langcode
|
||||
* (optional) The langcode of the request.
|
||||
*/
|
||||
protected function assertNo404Row($path, $langcode = 'en') {
|
||||
$this->assert404RowHelper($path, $langcode, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes if the row with the given parameters is in the redirect_404 table.
|
||||
*
|
||||
* @param string $path
|
||||
* The path of the request.
|
||||
* @param string $langcode
|
||||
* (optional) The langcode of the request.
|
||||
* @param bool $not_exists
|
||||
* (optional) TRUE if this 404 row should not exist in the redirect_404
|
||||
* table, FALSE if it should. Defaults to TRUE.
|
||||
*/
|
||||
protected function assert404RowHelper($path, $langcode = 'en', $not_exists = TRUE) {
|
||||
$result = db_select('redirect_404', 'r404')
|
||||
->fields('r404', ['path'])
|
||||
->condition('path', $path)
|
||||
->condition('langcode', $langcode)
|
||||
->condition('resolved', 0)
|
||||
->execute()
|
||||
->fetchField();
|
||||
|
||||
if ($not_exists) {
|
||||
$this->assertNotEquals($path, $result);
|
||||
}
|
||||
else {
|
||||
$this->assertEquals($path, $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\redirect_404\Unit;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\redirect_404\SqlRedirectNotFoundStorage;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* Tests that overly long paths aren't logged.
|
||||
*
|
||||
* @group redirect_404
|
||||
*/
|
||||
class SqlRedirectNotFoundStorageTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Mock database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* Mock config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->database = $this->getMockBuilder(Connection::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that long paths aren't stored in the database.
|
||||
*/
|
||||
public function testLongPath() {
|
||||
$this->database->expects($this->never())
|
||||
->method('merge');
|
||||
$storage = new SqlRedirectNotFoundStorage($this->database, $this->getConfigFactoryStub());
|
||||
$storage->logRequest($this->randomMachineName(SqlRedirectNotFoundStorage::MAX_PATH_LENGTH + 1), LanguageInterface::LANGCODE_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that invalid UTF-8 paths are not stored in the database.
|
||||
*/
|
||||
public function testInvalidUtf8Path() {
|
||||
$this->database->expects($this->never())
|
||||
->method('merge');
|
||||
$storage = new SqlRedirectNotFoundStorage($this->database, $this->getConfigFactoryStub());
|
||||
$storage->logRequest("Caf\xc3", LanguageInterface::LANGCODE_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that all logs are kept if row limit config is "All".
|
||||
*/
|
||||
public function testPurgeOldRequests() {
|
||||
$this->configFactory = $this->getConfigFactoryStub(
|
||||
[
|
||||
'redirect_404.settings' => [
|
||||
'row_limit' => 0,
|
||||
],
|
||||
]
|
||||
);
|
||||
$storage = new SqlRedirectNotFoundStorage($this->database, $this->configFactory);
|
||||
$storage->purgeOldRequests();
|
||||
$this->database->expects($this->never())
|
||||
->method('select');
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
domain_redirects: { }
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
redirect_domain.domains:
|
||||
type: config_object
|
||||
label: 'Redirect domains'
|
||||
mapping:
|
||||
domain_redirects:
|
||||
type: sequence
|
||||
label: 'Domain redirects'
|
||||
sequence:
|
||||
type: sequence
|
||||
sequence:
|
||||
type: mapping
|
||||
mapping:
|
||||
sub_path:
|
||||
type: string
|
||||
label: 'Sub path'
|
||||
destination:
|
||||
type: string
|
||||
label: 'Destination'
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
name: 'Redirect Domain'
|
||||
type: module
|
||||
description: 'Allows users to redirect between domains.'
|
||||
# core: 8.x
|
||||
|
||||
dependencies:
|
||||
- redirect
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-03-12
|
||||
version: '8.x-1.1'
|
||||
core: '8.x'
|
||||
project: 'redirect'
|
||||
datestamp: 1520897884
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
redirect_domain.domain_list:
|
||||
title: 'Domain redirects'
|
||||
base_route: redirect.list
|
||||
route_name: redirect_domain.domain_list
|
||||
weight: 60
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function redirect_domain_help($route_name, RouteMatchInterface $route_match) {
|
||||
$output = '';
|
||||
switch ($route_name) {
|
||||
case 'help.page.redirect_domain':
|
||||
$output = '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Redirect domain module allows users to redirect between domains.') . '</p>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dt>' . t('Manage domain redirects') . '</dt>';
|
||||
$output .= '<dd>' . t('The domain redirect is accessed through <a href=":domainlist">Domain Redirects</a>. The user can add the domain redirects through the domain redirect table which consists of the domain from which it needs to be redirected, the sub path and the complete url destination to which it needs to be redirected. The module also supports the usage of a wildcard redirecting, thus many requests can be handled with one instance of domain redirect.', [':domainlist' => Url::fromRoute('redirect_domain.domain_list')->toString()]) . '</dd>';
|
||||
return $output;
|
||||
case 'redirect_domain.domain_list':
|
||||
$output = '<p>' . t('The domain redirect table consists of the domain from which it needs to be redirected, the sub path and the complete url destination to which it needs to be redirected.') . '</p>';
|
||||
$output .= '<h5>' . t('Example Configuration') . '</h5>';
|
||||
$output .= '<ul>';
|
||||
$output .= '<li> example.com/redirect => redirected.com/example-path </li>';
|
||||
$output .= '<li> foo.com/* => bar.com </li>';
|
||||
$output .= '</ul>';
|
||||
$output .= '<h5>' . t('Example Redirects') . '</h5>';
|
||||
$output .= '<ul>';
|
||||
$output .= '<li>' . t('Request: example.com/redirect => Response: redirected.com/example-path') . '</li>';
|
||||
$output .= '<li>' . t('Request: foo.com/any-path => Response: bar.com') . '</li>';
|
||||
$output .= '</ul>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
redirect_domain.domain_list:
|
||||
path: '/admin/config/search/redirect/domain'
|
||||
defaults:
|
||||
_title: 'Domain redirects'
|
||||
_form: '\Drupal\redirect_domain\Form\RedirectDomainForm'
|
||||
requirements:
|
||||
_permission: 'administer redirects'
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
services:
|
||||
redirect_domain.request_subscriber:
|
||||
class: Drupal\redirect_domain\EventSubscriber\DomainRedirectRequestSubscriber
|
||||
arguments: ['@config.factory', '@redirect.checker', '@path.matcher']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_domain\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Path\PathMatcherInterface;
|
||||
use Drupal\Core\Routing\TrustedRedirectResponse;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\redirect\RedirectChecker;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Redirect subscriber for controller requests.
|
||||
*/
|
||||
class DomainRedirectRequestSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\redirect\RedirectChecker
|
||||
*/
|
||||
protected $redirectChecker;
|
||||
|
||||
/**
|
||||
* Domain redirect configuration.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $domainConfig;
|
||||
|
||||
/**
|
||||
* The path matcher.
|
||||
*
|
||||
* @var \Drupal\Core\Path\PathMatcherInterface
|
||||
*/
|
||||
protected $pathMatcher;
|
||||
|
||||
/**
|
||||
* Redirect configuration.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $redirectConfig;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\redirect\EventSubscriber\RedirectRequestSubscriber object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory.
|
||||
* @param \Drupal\redirect\RedirectChecker $redirect_checker
|
||||
* The redirect checker service.
|
||||
* @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
|
||||
* The path matcher.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, RedirectChecker $redirect_checker, PathMatcherInterface $path_matcher) {
|
||||
$this->domainConfig = $config_factory->get('redirect_domain.domains');
|
||||
$this->redirectConfig = $config_factory->get('redirect.settings');
|
||||
$this->redirectChecker = $redirect_checker;
|
||||
$this->pathMatcher = $path_matcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the domain redirect if any found.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
|
||||
* The event to process.
|
||||
*/
|
||||
public function onKernelRequestCheckDomainRedirect(GetResponseEvent $event) {
|
||||
$request = clone $event->getRequest();
|
||||
|
||||
if (!$this->redirectChecker->canRedirect($request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Redirect between domains configuration.
|
||||
$domains = $this->domainConfig->get('domain_redirects');
|
||||
if (!empty($domains)) {
|
||||
$host = $request->getHost();
|
||||
$path = $request->getPathInfo();
|
||||
$protocol = $request->getScheme() . '://';
|
||||
$destination = NULL;
|
||||
|
||||
// Checks if there is a redirect domain in the configuration.
|
||||
if (isset($domains[str_replace('.', ':', $host)])) {
|
||||
foreach ($domains[str_replace('.', ':', $host)] as $item) {
|
||||
if ($this->pathMatcher->matchPath($path, $item['sub_path'])) {
|
||||
$destination = $item['destination'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($destination) {
|
||||
// Use the default status code from Redirect.
|
||||
$response = new TrustedRedirectResponse(
|
||||
$protocol . $destination,
|
||||
$this->redirectConfig->get('default_status_code')
|
||||
);
|
||||
$event->setResponse($response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prior to set the response it check if we can redirect.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
|
||||
* The event object.
|
||||
* @param \Drupal\Core\Url $url
|
||||
* The Url where we want to redirect.
|
||||
*/
|
||||
protected function setResponse(GetResponseEvent $event, Url $url) {
|
||||
$request = $event->getRequest();
|
||||
|
||||
parse_str($request->getQueryString(), $query);
|
||||
$url->setOption('query', $query);
|
||||
$url->setAbsolute(TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
// This needs to run before RouterListener::onKernelRequest(), which has
|
||||
// a priority of 32 and
|
||||
// RedirectRequestSubscriber::onKernelRequestCheckRedirect(), which has
|
||||
// a priority of 33. Otherwise, that aborts the request if no matching
|
||||
// route is found.
|
||||
$events[KernelEvents::REQUEST][] = ['onKernelRequestCheckDomainRedirect', 34];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_domain\Form;
|
||||
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a redirect domain configuration form.
|
||||
*/
|
||||
class RedirectDomainForm extends ConfigFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'form_redirect_domain_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return [
|
||||
'redirect_domain.domains',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
if (!$form_state->has('maximum_domains')) {
|
||||
$form_state->set('maximum_domains', 1);
|
||||
}
|
||||
|
||||
$form['redirects'] = [
|
||||
'#type' => 'table',
|
||||
'#tree' => TRUE,
|
||||
'#header' => [
|
||||
$this->t('From domain'),
|
||||
$this->t('Sub path'),
|
||||
$this->t('Destination')
|
||||
],
|
||||
'#prefix' => '<div id="redirect-domain-wrapper">',
|
||||
'#suffix' => '</div>',
|
||||
];
|
||||
|
||||
$rows = [];
|
||||
// Obtain domain redirects from configuration.
|
||||
if ($domain_redirects = $this->config('redirect_domain.domains')->get('domain_redirects')) {
|
||||
foreach ($domain_redirects as $key => $value) {
|
||||
foreach ($value as $item) {
|
||||
$form['redirects'][] = [
|
||||
'from' => [
|
||||
'#type' => 'textfield',
|
||||
'#value' => str_replace(':','.',$key),
|
||||
],
|
||||
'sub_path' => [
|
||||
'#type' => 'textfield',
|
||||
'#value' => $item['sub_path'],
|
||||
],
|
||||
'destination' => [
|
||||
'#type' => 'textfield',
|
||||
'#value' => $item['destination'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fields for the new domain redirects.
|
||||
for ($i = 0; $i < $form_state->get('maximum_domains'); $i++) {
|
||||
$form['redirects'][] = [
|
||||
'from' => [
|
||||
'#type' => 'textfield',
|
||||
],
|
||||
'sub_path' => [
|
||||
'#type' => 'textfield',
|
||||
'#value' => '/',
|
||||
],
|
||||
'destination' => [
|
||||
'#type' => 'textfield',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$form['add'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Add another'),
|
||||
'#submit' => ['::addAnotherSubmit'],
|
||||
'#ajax' => [
|
||||
'callback' => '::ajaxAddAnother',
|
||||
'wrapper' => 'redirect-domain-wrapper',
|
||||
],
|
||||
];
|
||||
$form['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#button_type' => 'primary',
|
||||
'#value' => $this->t('Save'),
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax callback for adding another domain redirect.
|
||||
*
|
||||
* @param array $form
|
||||
* The form structure.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*
|
||||
* @return array
|
||||
* The new domain redirect form part.
|
||||
*/
|
||||
public function ajaxAddAnother(array $form, FormStateInterface $form_state) {
|
||||
return $form['redirects'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for adding a new domain field.
|
||||
*/
|
||||
public function addAnotherSubmit(array $form, FormStateInterface $form_state) {
|
||||
$form_state->set('maximum_domains', $form_state->get('maximum_domains') + 1);
|
||||
$form_state->setRebuild(TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::validateForm($form, $form_state);
|
||||
if ($redirects = $form_state->getValue('redirects')) {
|
||||
foreach ($redirects as $redirect) {
|
||||
if (strpos($redirect['from'], '://') !== FALSE) {
|
||||
$form_state->setErrorByName('redirects', t('No protocol should be included in the redirect domain.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$domain_redirects = [];
|
||||
$domain_config = $this->config('redirect_domain.domains');
|
||||
|
||||
if ($redirects = $form_state->getValue('redirects')) {
|
||||
foreach ($redirects as $redirect) {
|
||||
if (!empty($redirect['from']) && !empty($redirect['destination'])) {
|
||||
// Replace '.' with ':' for an eligible key.
|
||||
$redirect['from'] = str_replace('.', ':', $redirect['from']);
|
||||
$domain_redirects[$redirect['from']][] = [
|
||||
'sub_path' => '/' . ltrim($redirect['sub_path'], '/'),
|
||||
'destination' => $redirect['destination']
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
$domain_config->set('domain_redirects', $domain_redirects);
|
||||
$domain_config->save();
|
||||
drupal_set_message(t('The domain redirects have been saved.'));
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\redirect_domain\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests the UI for domain redirect.
|
||||
*
|
||||
* @group redirect_domain
|
||||
*/
|
||||
class RedirectDomainUITest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'redirect_domain',
|
||||
];
|
||||
|
||||
/**
|
||||
* Tests domain redirect.
|
||||
*/
|
||||
public function testDomainRedirect() {
|
||||
$user = $this->drupalCreateUser([
|
||||
'administer site configuration',
|
||||
'access administration pages',
|
||||
'administer redirects'
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
$this->drupalGet('/admin/config/search/redirect/domain');
|
||||
|
||||
// Assert that there are 2 domain redirect fields.
|
||||
$this->assertFieldByName('redirects[0][from]');
|
||||
$this->assertFieldByName('redirects[0][sub_path]');
|
||||
$this->assertFieldByName('redirects[0][destination]');
|
||||
|
||||
// Add another field for new domain redirect.
|
||||
$this->drupalPostAjaxForm(NULL, [], ['op' => t('Add another')]);
|
||||
|
||||
// Add two new domain redirects.
|
||||
$edit = [
|
||||
'redirects[0][from]' => 'foo.example.org',
|
||||
'redirects[0][sub_path]' => '//sub-path',
|
||||
'redirects[0][destination]' => 'www.example.org/foo',
|
||||
'redirects[1][from]' => 'bar.example.org',
|
||||
'redirects[1][sub_path]' => '',
|
||||
'redirects[1][destination]' => 'www.example.org/bar',
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
|
||||
// Check the new domain redirects.
|
||||
$this->assertFieldByName('redirects[0][from]', 'foo.example.org');
|
||||
$this->assertFieldByName('redirects[0][destination]', 'www.example.org/foo');
|
||||
$this->assertFieldByName('redirects[1][from]', 'bar.example.org');
|
||||
$this->assertFieldByName('redirects[1][destination]', 'www.example.org/bar');
|
||||
|
||||
// Ensure that the sub paths are correct.
|
||||
$this->assertFieldByName('redirects[0][sub_path]', '/sub-path');
|
||||
$this->assertFieldByName('redirects[1][sub_path]', '/');
|
||||
}
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\redirect_domain\Unit;
|
||||
|
||||
use Drupal\Core\Path\PathMatcher;
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\redirect\RedirectChecker;
|
||||
use Drupal\redirect_domain\EventSubscriber\DomainRedirectRequestSubscriber;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
|
||||
/**
|
||||
* Tests the redirect logic.
|
||||
*
|
||||
* @group redirect_domain
|
||||
*
|
||||
* @coversDefaultClass Drupal\redirect_domain\EventSubscriber\DomainRedirectRequestSubscriber
|
||||
*/
|
||||
class DomainRedirectRequestSubscriberTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests redirect between domains.
|
||||
*
|
||||
* @dataProvider providerDomains
|
||||
*/
|
||||
public function testDomainRedirect($request_url, $response_url) {
|
||||
$data = [
|
||||
'redirect_domain.domains' => [
|
||||
'domain_redirects' => [
|
||||
'foo:com' => [
|
||||
[
|
||||
'sub_path' => '/fixedredirect',
|
||||
'destination' => 'bar.com/fixedredirect',
|
||||
],
|
||||
[
|
||||
'sub_path' => '/*',
|
||||
'destination' => 'bar.com/example',
|
||||
],
|
||||
],
|
||||
'example:com' => [
|
||||
[
|
||||
'sub_path' => '/foo/*/bar',
|
||||
'destination' => 'example.com/bar/foo',
|
||||
],
|
||||
],
|
||||
'simpleexample:com' => [
|
||||
[
|
||||
'sub_path' => '/redirect',
|
||||
'destination' => 'redirected.com/redirect',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'redirect.settings' => [
|
||||
'default_status_code' => 301,
|
||||
],
|
||||
'system.site' => [
|
||||
'page.front' => '/',
|
||||
],
|
||||
];
|
||||
|
||||
// Create a mock redirect checker.
|
||||
$checker = $this->getMockBuilder(RedirectChecker::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$checker->expects($this->any())
|
||||
->method('canRedirect')
|
||||
->will($this->returnValue(TRUE));
|
||||
|
||||
// Set up the configuration for the requested domain.
|
||||
$config_factory = $this->getConfigFactoryStub($data);
|
||||
|
||||
// Create a mock path matcher.
|
||||
$route_match = $this->getMock(RouteMatchInterface::class);
|
||||
$path_matcher = new PathMatcher($config_factory, $route_match);
|
||||
|
||||
$subscriber = new DomainRedirectRequestSubscriber(
|
||||
$config_factory,
|
||||
$checker,
|
||||
$path_matcher
|
||||
);
|
||||
|
||||
// Make a request to the urls from the data provider and get the response.
|
||||
$event = $this->getGetResponseEventStub($request_url, http_build_query([]));
|
||||
|
||||
// Run the main redirect method.
|
||||
$subscriber->onKernelRequestCheckDomainRedirect($event);
|
||||
|
||||
// Assert the expected response from the data provider.
|
||||
if ($response_url) {
|
||||
$this->assertTrue($event->getResponse() instanceof RedirectResponse);
|
||||
$response = $event->getResponse();
|
||||
// Make sure that the response is properly redirected.
|
||||
$this->assertEquals($response_url, $response->getTargetUrl());
|
||||
$this->assertEquals(
|
||||
$config_factory->get('redirect.settings')->get('default_status_code'),
|
||||
$response->getStatusCode()
|
||||
);
|
||||
}
|
||||
else {
|
||||
$this->assertNull($event->getResponse());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets response event object.
|
||||
*
|
||||
* @param $path_info
|
||||
* The path info.
|
||||
* @param $query_string
|
||||
* The query string in the url.
|
||||
*
|
||||
* @return GetResponseEvent
|
||||
* The response for the request.
|
||||
*/
|
||||
protected function getGetResponseEventStub($path_info, $query_string) {
|
||||
$request = Request::create($path_info . '?' . $query_string, 'GET', [], [], [], ['SCRIPT_NAME' => 'index.php']);
|
||||
|
||||
$http_kernel = $this->getMockBuilder(HttpKernelInterface::class)
|
||||
->getMock();
|
||||
return new GetResponseEvent($http_kernel, $request, 'test');
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the domain redirects.
|
||||
*
|
||||
* @return array
|
||||
* An array of requests and expected responses for the redirect domains.
|
||||
*/
|
||||
public function providerDomains() {
|
||||
$datasets = [];
|
||||
$datasets[] = ['http://foo.com/example', 'http://bar.com/example'];
|
||||
$datasets[] = ['http://example.com/foo/test/bar', 'http://example.com/bar/foo'];
|
||||
$datasets[] = ['http://simpleexample.com/redirect', 'http://redirected.com/redirect'];
|
||||
$datasets[] = ['http://nonexisting.com', NULL];
|
||||
$datasets[] = ['http://simpleexample.com/wrongpath', NULL];
|
||||
$datasets[] = ['http://foo.com/fixedredirect', 'http://bar.com/fixedredirect'];
|
||||
return $datasets;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks provided by the Redirect module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup redirect_api_hooks Redirect API Hooks
|
||||
* @{
|
||||
* During redirect operations (create, update, view, delete, etc.), there are
|
||||
* several sets of hooks that get invoked to allow modules to modify the
|
||||
* redirect operation:
|
||||
* - All-module hooks: Generic hooks for "redirect" operations. These are
|
||||
* always invoked on all modules.
|
||||
* - Entity hooks: Generic hooks for "entity" operations. These are always
|
||||
* invoked on all modules.
|
||||
*
|
||||
* Here is a list of the redirect and entity hooks that are invoked, and other
|
||||
* steps that take place during redirect operations:
|
||||
* - Creating a new redirect (calling redirect_save() on a new redirect):
|
||||
* - hook_redirect_presave() (all)
|
||||
* - Redirect written to the database
|
||||
* - hook_redirect_insert() (all)
|
||||
* - hook_entity_insert() (all)
|
||||
* - Updating an existing redirect (calling redirect_save() on an existing redirect):
|
||||
* - hook_redirect_presave() (all)
|
||||
* - Redirect written to the database
|
||||
* - hook_redirect_update() (all)
|
||||
* - hook_entity_update() (all)
|
||||
* - Loading a redirect (calling redirect_load(), redirect_load_multiple(), or
|
||||
* entity_load() with $entity_type of 'redirect'):
|
||||
* - Redirect information is read from database.
|
||||
* - hook_entity_load() (all)
|
||||
* - hook_redirect_load() (all)
|
||||
* - Deleting a redirect (calling redirect_delete() or redirect_delete_multiple()):
|
||||
* - Redirect is loaded (see Loading section above)
|
||||
* - Redirect information is deleted from database
|
||||
* - hook_redirect_delete() (all)
|
||||
* - hook_entity_delete() (all)
|
||||
* - Preparing a redirect for editing (note that if it's
|
||||
* an existing redirect, it will already be loaded; see the Loading section
|
||||
* above):
|
||||
* - hook_redirect_prepare() (all)
|
||||
* - Validating a redirect during editing form submit (calling
|
||||
* redirect_form_validate()):
|
||||
* - hook_redirect_validate() (all)
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup hooks
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Act on redirects being loaded from the database.
|
||||
*
|
||||
* This hook is invoked during redirect loading, which is handled by
|
||||
* entity_load(), via classes RedirectController and
|
||||
* DrupalDefaultEntityController. After the redirect information is read from
|
||||
* the database or the entity cache, hook_entity_load() is invoked on all
|
||||
* implementing modules, and then hook_redirect_load() is invoked on all
|
||||
* implementing modules.
|
||||
*
|
||||
* This hook should only be used to add information that is not in the redirect
|
||||
* table, not to replace information that is in that table (which could
|
||||
* interfere with the entity cache). For performance reasons, information for
|
||||
* all available redirects should be loaded in a single query where possible.
|
||||
*
|
||||
* The $types parameter allows for your module to have an early return (for
|
||||
* efficiency) if your module only supports certain redirect types.
|
||||
*
|
||||
* @param $redirects
|
||||
* An array of the redirects being loaded, keyed by rid.
|
||||
* @param $types
|
||||
* An array containing the types of the redirects.
|
||||
*
|
||||
* @ingroup redirect_api_hooks
|
||||
*/
|
||||
function hook_redirect_load(array &$redirects, $types) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the list of redirects matching a certain source.
|
||||
*
|
||||
* @param $redirects
|
||||
* An array of redirect objects.
|
||||
* @param $source
|
||||
* The source request path.
|
||||
* @param $context
|
||||
* An array with the following key/value pairs:
|
||||
* - language: The language code of the source request.
|
||||
* - query: An array of the source request query string.
|
||||
*
|
||||
* @see redirect_load_by_source()
|
||||
* @ingroup redirect_api_hooks
|
||||
*/
|
||||
function hook_redirect_load_by_source_alter(array &$redirects, $source, array $context) {
|
||||
foreach ($redirects as $rid => $redirect) {
|
||||
if ($redirect->source !== $source) {
|
||||
// If the redirects to do not exactly match $source (e.g. case
|
||||
// insensitive matches), then remove them from the results.
|
||||
unset($redirects[$rid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on a redirect object about to be shown on the add/edit form.
|
||||
*
|
||||
* This hook is invoked from redirect_create().
|
||||
*
|
||||
* @param $redirect
|
||||
* The redirect that is about to be shown on the add/edit form.
|
||||
*
|
||||
* @ingroup redirect_api_hooks
|
||||
*/
|
||||
function hook_redirect_prepare($redirect) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on a redirect being redirected.
|
||||
*
|
||||
* This hook is invoked from redirect_redirect() before the redirect callback
|
||||
* is invoked.
|
||||
*
|
||||
* @param $redirect
|
||||
* The redirect that is being used for the redirect.
|
||||
*
|
||||
* @see redirect_redirect()
|
||||
* @see drupal_page_is_cacheable()
|
||||
* @ingroup redirect_api_hooks
|
||||
*/
|
||||
function hook_redirect_alter($redirect) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup hooks".
|
||||
*/
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Drush integration for the redirect module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_drush_command().
|
||||
*/
|
||||
function redirect_drush_command() {
|
||||
$items['generate-redirects'] = array(
|
||||
'description' => 'Create redirects.',
|
||||
'drupal dependencies' => array('devel_generate'),
|
||||
'arguments' => array(
|
||||
'count' => 'Number of redirects to generate.',
|
||||
),
|
||||
'options' => array(
|
||||
'delete' => 'Delete all redirects before generating new ones.',
|
||||
),
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Command callback. Generate a number of redirects.
|
||||
*/
|
||||
function drush_redirect_generate_redirects($count = NULL) {
|
||||
if (drush_generate_is_number($count) == FALSE) {
|
||||
return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', t('Invalid number of redirects.'));
|
||||
}
|
||||
module_load_include('inc', 'redirect', 'redirect.generate');
|
||||
drush_generate_include_devel();
|
||||
redirect_run_unprogressive_batch('redirect_generate_redirects_batch_info', $count, drush_get_option('delete'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an unprogressive batch process for CLI.
|
||||
*/
|
||||
function redirect_run_unprogressive_batch() {
|
||||
$batch = batch_get();
|
||||
if (!empty($batch)) {
|
||||
// If there is already something in the batch, don't run.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$args = func_get_args();
|
||||
$batch_callback = array_shift($args);
|
||||
|
||||
if (!lock_acquire($batch_callback)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Attempt to increase the execution time.
|
||||
drupal_set_time_limit(240);
|
||||
|
||||
// Build the batch array.
|
||||
$batch = call_user_func_array($batch_callback, $args);
|
||||
batch_set($batch);
|
||||
|
||||
// We need to manually set the progressive variable again.
|
||||
// @todo Remove when http://drupal.org/node/638712 is fixed.
|
||||
$batch =& batch_get();
|
||||
$batch['progressive'] = FALSE;
|
||||
|
||||
// Run the batch process.
|
||||
batch_process();
|
||||
|
||||
lock_release($batch_callback);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Generate callbacks for the redirect module.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Random;
|
||||
use Drupal\devel_generate\DevelGenerateBase;
|
||||
use Drupal\redirect\Entity\Redirect;
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Devel generate integration for the redirect module.
|
||||
*/
|
||||
|
||||
function redirect_generate_form() {
|
||||
$form['count'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('How many URL redirects would you like to generate?'),
|
||||
'#default_value' => 50,
|
||||
'#size' => 4,
|
||||
);
|
||||
$form['delete'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Delete all URL redirects before generating new URL redirects.'),
|
||||
'#default_value' => FALSE,
|
||||
);
|
||||
$form['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Generate'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
function redirect_generate_form_submit(&$form, &$form_state) {
|
||||
// Run the batch.
|
||||
$batch = redirect_generate_redirects_batch_info($form_state['values']['count'], $form_state['values']['delete']);
|
||||
batch_set($batch);
|
||||
}
|
||||
|
||||
function redirect_generate_redirects_batch_info($count, $delete = FALSE) {
|
||||
if ($delete) {
|
||||
$operations[] = array('redirect_generate_batch_delete', array());
|
||||
}
|
||||
|
||||
$operations[] = array('redirect_generate_batch_generate', array($count));
|
||||
|
||||
return array(
|
||||
'operations' => $operations,
|
||||
'finished' => 'redirect_generate_batch_finished',
|
||||
'file' => drupal_get_path('module', 'redirect') . '/redirect.generate.inc',
|
||||
);
|
||||
}
|
||||
|
||||
function redirect_generate_batch_delete(array &$context) {
|
||||
if (empty($context['sandbox'])) {
|
||||
$context['sandbox'] = array();
|
||||
$context['sandbox']['progress'] = 0;
|
||||
$context['sandbox']['current_rid'] = 0;
|
||||
$context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT rid) FROM {redirect}')->fetchField();
|
||||
}
|
||||
|
||||
$limit = 20;
|
||||
$rids = db_query_range("SELECT rid FROM {redirect} WHERE rid > :rid ORDER BY rid", 0, $limit, array(':rid' => $context['sandbox']['current_rid']))->fetchCol();
|
||||
foreach (redirect_repository()->loadMultiple($rids) as $redirect) {
|
||||
$redirect->delete();
|
||||
}
|
||||
|
||||
// Update our progress information.
|
||||
$context['sandbox']['progress'] += count($rids);
|
||||
$context['sandbox']['current_rid'] = end($rids);
|
||||
$context['message'] = t('Deleted URL redirect @rid.', array('@rid' => end($rids)));
|
||||
|
||||
// Inform the batch engine that we are not finished,
|
||||
// and provide an estimation of the completion level we reached.
|
||||
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
|
||||
$context['finished'] = ($context['sandbox']['progress'] >= $context['sandbox']['max']);
|
||||
}
|
||||
}
|
||||
|
||||
function redirect_generate_batch_generate($num, array &$context) {
|
||||
if (empty($context['sandbox'])) {
|
||||
$context['sandbox'] = array();
|
||||
$context['sandbox']['progress'] = 0;
|
||||
$context['sandbox']['max'] = $num;
|
||||
$query = \Drupal::database()->select('node', 'n');
|
||||
$query->addField('n', 'nid');
|
||||
$query->condition('n.status', NODE_PUBLISHED);
|
||||
$query->addTag('node_access');
|
||||
$context['sandbox']['nids'] = $query->execute()->fetchAllKeyed(0, 0);
|
||||
}
|
||||
|
||||
module_load_include('inc', 'devel_generate');
|
||||
|
||||
$limit = 20;
|
||||
$types = array_keys(redirect_status_code_options());
|
||||
$languages = \Drupal::moduleHandler()->moduleExists('locale') ? array_keys(\Drupal::languageManager()->getLanguages()) : array();
|
||||
|
||||
for ($i = 0; $i < min($limit, $context['sandbox']['max'] - $context['sandbox']['progress']); $i++) {
|
||||
$rand = mt_rand(0, 100);
|
||||
|
||||
$redirect = Redirect::create();
|
||||
|
||||
$source = _redirect_generate_url();
|
||||
$source_options = array();
|
||||
$redirect_options = array();
|
||||
|
||||
if ($context['sandbox']['nids'] && $rand >= 40) {
|
||||
$redirect_target = 'node/' . array_rand($context['sandbox']['nids']);
|
||||
}
|
||||
else {
|
||||
$redirect_target = _redirect_generate_url(TRUE);
|
||||
if ($rand <= 20) {
|
||||
$redirect_options['query'] = _redirect_generate_querystring();
|
||||
}
|
||||
if ($rand <= 5) {
|
||||
$redirect_options['fragment'] = DevelGenerateBase::generateWord(mt_rand(4, 8));
|
||||
}
|
||||
}
|
||||
|
||||
if ($rand <= 20) {
|
||||
$redirect->setStatusCode($types[array_rand($types)]);
|
||||
}
|
||||
|
||||
if ($languages && $rand <= 20) {
|
||||
$redirect->setLanguage($languages[array_rand($languages)]);
|
||||
}
|
||||
|
||||
$query = array();
|
||||
if ($rand <= 30) {
|
||||
$query = _redirect_generate_querystring();
|
||||
}
|
||||
|
||||
$redirect->setSource($source, $query);
|
||||
$redirect->setRedirect($redirect_target);
|
||||
|
||||
$redirect->save();
|
||||
|
||||
if (mt_rand(0, 1)) {
|
||||
$query = \Drupal::database();
|
||||
$query->update('redirect')
|
||||
->fields(array(
|
||||
'count' => mt_rand(1, 500),
|
||||
'access' => mt_rand(REQUEST_TIME - 31536000, REQUEST_TIME),
|
||||
))
|
||||
->condition('rid', $redirect->id())
|
||||
->execute();
|
||||
}
|
||||
|
||||
$context['results'][] = $redirect->id();
|
||||
}
|
||||
|
||||
// Update our progress information.
|
||||
$context['sandbox']['progress'] += $limit;
|
||||
//$context['message'] = t('Deleted URL redirect @rid.', array('@rid' => end($rids)));
|
||||
|
||||
// Inform the batch engine that we are not finished,
|
||||
// and provide an estimation of the completion level we reached.
|
||||
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
|
||||
$context['finished'] = ($context['sandbox']['progress'] >= $context['sandbox']['max']);
|
||||
}
|
||||
}
|
||||
|
||||
function redirect_generate_batch_finished($success, $results, $operations) {
|
||||
if ($success) {
|
||||
drupal_set_message(\Drupal::translation()->formatPlural(count($results), 'One URL redirect created.', '@count URL redirects created.'));
|
||||
}
|
||||
else {
|
||||
// An error occurred.
|
||||
// $operations contains the operations that remained unprocessed.
|
||||
$error_operation = reset($operations);
|
||||
drupal_set_message(t('An error occurred while processing @operation with arguments : @args', array('@operation' => $error_operation[0], '@args' => print_r($error_operation[0], TRUE))));
|
||||
}
|
||||
}
|
||||
|
||||
function _redirect_generate_url($external = FALSE, $max_levels = 2) {
|
||||
$url = array();
|
||||
if ($external) {
|
||||
$tlds = array('com', 'net', 'org');
|
||||
$url[] = 'http://www.example.'. $tlds[array_rand($tlds)];
|
||||
}
|
||||
$max_levels = mt_rand($external ? 0 : 1, $max_levels);
|
||||
for ($i = 1; $i <= $max_levels; $i++) {
|
||||
$url[] = DevelGenerateBase::generateWord(mt_rand(6 / $i, 8));
|
||||
}
|
||||
return implode('/', $url);
|
||||
}
|
||||
|
||||
function _redirect_generate_querystring() {
|
||||
$query = array(DevelGenerateBase::generateWord(mt_rand(1, 3)) => DevelGenerateBase::generateWord(mt_rand(2, 4)));
|
||||
return $query;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
name: Redirect
|
||||
type: module
|
||||
description: Allows users to redirect from old URLs to new URLs.
|
||||
# core: 8.x
|
||||
configure: redirect.settings
|
||||
|
||||
dependencies:
|
||||
- drupal:link
|
||||
- drupal:views
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-03-12
|
||||
version: '8.x-1.1'
|
||||
core: '8.x'
|
||||
project: 'redirect'
|
||||
datestamp: 1520897884
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Update hooks for the Redirect module.
|
||||
*/
|
||||
|
||||
use Drupal\redirect\Entity\Redirect;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\system\Entity\Action;
|
||||
use Drupal\views\Entity\View;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Drupal\Core\Config\InstallStorage;
|
||||
use Drupal\Core\Config\FileStorage;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Rehash redirects to account for case insensitivity.
|
||||
*/
|
||||
function redirect_update_8100(&$sandbox) {
|
||||
// Loop through 100 redirects at a time.
|
||||
if (!isset($sandbox['progress'])) {
|
||||
$sandbox['progress'] = 0;
|
||||
$sandbox['current_rid'] = 0;
|
||||
// Note, because MySQL can treat `foo = LOWER(foo)`, all records must be checked.
|
||||
$sandbox['max'] = db_query('SELECT COUNT(1) FROM {redirect}')->fetchField();
|
||||
}
|
||||
|
||||
$result = \Drupal::database()->select('redirect', 'r')
|
||||
->fields('r', ['rid', 'redirect_source__path', 'redirect_source__query', 'language', 'hash'])
|
||||
->condition('rid', $sandbox['current_rid'], '>')
|
||||
->range(0, 100)
|
||||
->orderBy('rid', 'ASC')
|
||||
->execute();
|
||||
|
||||
foreach ($result as $row) {
|
||||
$query = !empty($row->redirect_source__query) ? unserialize($row->redirect_source__query): [];
|
||||
$new_hash = Redirect::generateHash($row->redirect_source__path, (array) $query, $row->language);
|
||||
if ($row->hash != $new_hash) {
|
||||
// Do a direct query to speed things up.
|
||||
$query = \Drupal::database();
|
||||
$query->update('redirect')
|
||||
->fields(['hash' => $new_hash])
|
||||
->condition('rid', $row->rid)
|
||||
->execute();
|
||||
}
|
||||
$sandbox['progress']++;
|
||||
$sandbox['current_rid'] = $row->rid;
|
||||
}
|
||||
// Reset caches.
|
||||
$sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the {redirect} table.
|
||||
*/
|
||||
function redirect_update_8101() {
|
||||
// Get the current schema, change the length.
|
||||
$key_value_store_schema = \Drupal::keyValue('entity.storage_schema.sql');
|
||||
$schema = $key_value_store_schema->get('redirect.field_schema_data.hash');
|
||||
$schema['redirect']['fields']['hash']['length'] = 64;
|
||||
|
||||
// Set the max_length of the hash column to 64 characters.
|
||||
Database::getConnection()
|
||||
->schema()
|
||||
->changeField('redirect', 'hash', 'hash', $schema['redirect']['fields']['hash']);
|
||||
|
||||
// Update the last installed field definition and field schema.
|
||||
/** @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface $key_value_store */
|
||||
\Drupal::entityManager()->clearCachedFieldDefinitions();
|
||||
$key_value_store_definition = \Drupal::keyValue('entity.definitions.installed');
|
||||
$storage_definitions = $key_value_store_definition->get('redirect.field_storage_definitions');
|
||||
$storage_definitions['hash'] = $storage_definition = \Drupal::entityManager()
|
||||
->getFieldStorageDefinitions('redirect')['hash'];
|
||||
$key_value_store_definition->set('redirect.field_storage_definitions', $storage_definitions);
|
||||
|
||||
// Update the stored schema.
|
||||
$key_value_store_schema->set('redirect.field_schema_data.hash', $schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update settings based on existing settings and Globalredirect settings.
|
||||
*/
|
||||
function redirect_update_8102() {
|
||||
// Load default configuration.
|
||||
$redirect_settings = \Drupal::config('redirect.settings');
|
||||
$globalredirect_settings = \Drupal::config('globalredirect.settings');
|
||||
$config_factory = \Drupal::configFactory();
|
||||
$redirect = $config_factory->getEditable('redirect.settings');
|
||||
$nonclean_to_clean = $redirect_settings->get('global_clean');
|
||||
$admin_path = $redirect_settings->get('global_admin_paths');
|
||||
$frontpage_redirect = $redirect_settings->get('global_home');
|
||||
$deslash = $redirect_settings->get('global_deslash');
|
||||
|
||||
$message = NULL;
|
||||
|
||||
// If Globalredirect configuration exists, use those settings.
|
||||
if ((!$globalredirect_settings->isNew())) {
|
||||
$access_check = $globalredirect_settings->get('access_check');
|
||||
$normalize_aliases = $globalredirect_settings->get('normalize_aliases');
|
||||
$content_location_header = $globalredirect_settings->get('content_location_header');
|
||||
$term_path_handler = $globalredirect_settings->get('term_path_handler');
|
||||
$deslash = $globalredirect_settings->get('deslash');
|
||||
$frontpage_redirect = $globalredirect_settings->get('frontpage_redirect');
|
||||
$nonclean_to_clean = $globalredirect_settings->get('nonclean_to_clean');
|
||||
|
||||
$redirect->set('access_check', $access_check);
|
||||
$redirect->set('normalize_aliases', $normalize_aliases);
|
||||
$redirect->set('content_location_header', $content_location_header);
|
||||
$redirect->set('term_path_handler', $term_path_handler);
|
||||
|
||||
$message = 'The Globalredirect module functionality has been merged into redirect, it should be uninstalled now.';
|
||||
}
|
||||
else {
|
||||
$redirect->set('access_check', FALSE);
|
||||
$redirect->set('normalize_aliases', TRUE);
|
||||
$redirect->set('content_location_header', FALSE);
|
||||
$redirect->set('term_path_handler', TRUE);
|
||||
}
|
||||
|
||||
// Update new redirect settings names.
|
||||
$redirect->set('nonclean_to_clean', $nonclean_to_clean);
|
||||
$redirect->set('admin_path', $admin_path);
|
||||
$redirect->set('frontpage_redirect', $frontpage_redirect);
|
||||
$redirect->set('deslash', $deslash);
|
||||
|
||||
// Remove old names of redirect settings.
|
||||
$redirect->clear('global_clean');
|
||||
$redirect->clear('global_admin_paths');
|
||||
$redirect->clear('global_home');
|
||||
$redirect->clear('global_deslash');
|
||||
|
||||
$redirect->save();
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the new default redirect view.
|
||||
*/
|
||||
function redirect_update_8103() {
|
||||
$message = NULL;
|
||||
|
||||
// Only create if the redirect view doesn't exist and views is enabled.
|
||||
if (!View::load('redirect') && \Drupal::moduleHandler()->moduleExists('views')) {
|
||||
$config_path = drupal_get_path('module', 'redirect') . '/config/install/views.view.redirect.yml';
|
||||
$data = Yaml::parse($config_path);
|
||||
\Drupal::configFactory()->getEditable('views.view.redirect')->setData($data)->save(TRUE);
|
||||
$message = 'The new redirect view has been created.';
|
||||
}
|
||||
else {
|
||||
$message = 'Not creating a redirect view since it already exists.';
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the bulk delete action to config.
|
||||
*/
|
||||
function redirect_update_8104() {
|
||||
if (!Action::load('redirect_delete_action')) {
|
||||
$entity_type_manager = \Drupal::entityTypeManager();
|
||||
$module_handler = \Drupal::moduleHandler();
|
||||
|
||||
// Save the bulk delete action to config.
|
||||
$config_install_path = $module_handler->getModule('redirect')->getPath() . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY;
|
||||
$storage = new FileStorage($config_install_path);
|
||||
$entity_type_manager
|
||||
->getStorage('action')
|
||||
->create($storage->read('system.action.redirect_delete_action'))
|
||||
->trustData()
|
||||
->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure to use the redirect_404 submodule.
|
||||
*/
|
||||
function redirect_update_8105() {
|
||||
\Drupal::service('module_installer')->install(['redirect_404']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes unnecessary settings from storage.
|
||||
*/
|
||||
function redirect_update_8106() {
|
||||
$config = \Drupal::configFactory()->getEditable('redirect.settings');
|
||||
$config->set('route_normalizer_enabled', $config->get('normalize_aliases'));
|
||||
$config->clear('term_path_handler');
|
||||
$config->clear('normalize_aliases');
|
||||
$config->clear('deslash');
|
||||
$config->clear('frontpage_redirect');
|
||||
$config->clear('nonclean_to_clean');
|
||||
$config->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update permissions.
|
||||
*
|
||||
* When splitting the permission for settings from the permission to view
|
||||
* the list of redirects, maintain the current permissions for sites which
|
||||
* already have this module installed.
|
||||
*/
|
||||
function redirect_update_8107() {
|
||||
if ($roles = Role::loadMultiple()) {
|
||||
foreach ($roles as $role) {
|
||||
if ($role->hasPermission('administer redirects')) {
|
||||
$role->grantPermission('administer redirect settings');
|
||||
$role->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes unnecessary settings.
|
||||
*/
|
||||
function redirect_update_8108() {
|
||||
$config = \Drupal::configFactory()->getEditable('redirect.settings');
|
||||
$config->clear('canonical');
|
||||
$config->clear('content_location_header');
|
||||
$config->save();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
drupal.redirect.admin:
|
||||
version: VERSION
|
||||
css:
|
||||
component:
|
||||
css/redirect.admin.css: {}
|
||||
@@ -0,0 +1,5 @@
|
||||
redirect.add:
|
||||
route_name: redirect.add
|
||||
title: 'Add redirect'
|
||||
appears_on:
|
||||
- 'redirect.list'
|
||||
@@ -0,0 +1,26 @@
|
||||
redirect.list:
|
||||
title: 'URL redirects'
|
||||
parent: system.admin_config_search
|
||||
description: 'Redirect users from one URL to another.'
|
||||
route_name: redirect.list
|
||||
menu_name: admin
|
||||
|
||||
redirect.add:
|
||||
title: 'Add redirect'
|
||||
parent: redirect.list
|
||||
route_name: redirect.add
|
||||
menu_name: admin
|
||||
|
||||
redirect.settings:
|
||||
title: 'Settings'
|
||||
parent: redirect.list
|
||||
route_name: redirect.settings
|
||||
description: 'Configure behavior for URL redirects.'
|
||||
menu_name: admin
|
||||
|
||||
#redirect.devel_generate:
|
||||
# title: 'Generate redirects'
|
||||
# parent: dblog.page_not_found
|
||||
# route_name: redirect.devel_generate
|
||||
# description: 'Generate a given number of redirects. Optionally delete current redirects.'
|
||||
# menu_name: admin
|
||||
@@ -0,0 +1,10 @@
|
||||
redirect.list:
|
||||
route_name: redirect.list
|
||||
base_route: redirect.list
|
||||
title: URL Redirects
|
||||
|
||||
redirect.settings:
|
||||
route_name: redirect.settings
|
||||
base_route: redirect.list
|
||||
title: Settings
|
||||
weight: 50
|
||||
@@ -0,0 +1,497 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* The redirect module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup redirect_api Redirection API
|
||||
* @{
|
||||
* Functions related to URL redirects.
|
||||
*
|
||||
* @} End of "defgroup redirect_api".
|
||||
*/
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Field\FieldItemList;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\redirect\Entity\Redirect;
|
||||
|
||||
/**
|
||||
* Implements hook_hook_info().
|
||||
*/
|
||||
function redirect_hook_info() {
|
||||
$hooks = array(
|
||||
'redirect_load',
|
||||
'redirect_load_by_source_alter',
|
||||
'redirect_access',
|
||||
'redirect_prepare',
|
||||
'redirect_validate',
|
||||
'redirect_presave',
|
||||
'redirect_insert',
|
||||
'redirect_update',
|
||||
'redirect_delete',
|
||||
'redirect_alter',
|
||||
);
|
||||
|
||||
return array_fill_keys($hooks, array('group' => 'redirect'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function redirect_help($route_name, RouteMatchInterface $route_match) {
|
||||
$output = '';
|
||||
switch ($route_name) {
|
||||
case 'help.page.redirect':
|
||||
$output = '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Redirect module allows users to redirect from old URLs to new URLs. For more information, see the <a href=":online">online documentation for Redirect</a>.', [':online' => 'https://www.drupal.org/documentation/modules/path-redirect']) . '</p>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dd>' . t('Redirect is accessed from three tabs that help you manage <a href=":list">URL Redirects</a>.', [':list' => Url::fromRoute('redirect.list')->toString()]) . '</dd>';
|
||||
$output .= '<dt>' . t('Manage URL Redirects') . '</dt>';
|
||||
$output .= '<dd>' . t('The <a href=":redirect">"URL Redirects"</a> page is used to setup and manage URL Redirects. New redirects are created here using the <a href=":add_form">Add redirect</a> button which presents a form to simplify the creation of redirects . The URL redirects page provides a list of all redirects on the site and allows you to edit them.', [':redirect' => Url::fromRoute('redirect.list')->toString(), ':add_form' => Url::fromRoute('redirect.add')->toString()]) . '</dd>';
|
||||
if (\Drupal::moduleHandler()->moduleExists('redirect_404')) {
|
||||
$output .= '<dt>' . t('Fix 404 pages') . '</dt>';
|
||||
$output .= '<dd>' . t('<a href=":fix_404">"Fix 404 pages"</a> lists all paths that have resulted in 404 errors and do not yet have any redirects assigned to them. This 404 (or Not Found) error message is an HTTP standard response code indicating that the client was able to communicate with a given server, but the server could not find what was requested.', [':fix_404' => Url::fromRoute('redirect_404.fix_404')->toString()]) . '</dd>';
|
||||
}
|
||||
elseif (!\Drupal::moduleHandler()->moduleExists('redirect_404') && \Drupal::currentUser()->hasPermission('administer modules')) {
|
||||
$output .= '<dt>' . t('Fix 404 pages') . '</dt>';
|
||||
$output .= '<dd>' . t('404 (or Not Found) error message is an HTTP standard response code indicating that the client was able to communicate with a given server, but the server could not find what was requested. Please install the <a href=":extend">Redirect 404</a> submodule to be able to log all paths that have resulted in 404 errors.', [':extend' => Url::fromRoute('system.modules_list')->toString()]) . '</dd>';
|
||||
}
|
||||
$output .= '<dt>' . t('Configure Global Redirects') . '</dt>';
|
||||
$output .= '<dd>' . t('The <a href=":settings">"Settings"</a> page presents you with a number of means to adjust redirect settings.', [':settings' => Url::fromRoute('redirect.settings')->toString()]) . '</dd>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_delete().
|
||||
*
|
||||
* Will delete redirects based on the entity URL.
|
||||
*/
|
||||
function redirect_entity_delete(EntityInterface $entity) {
|
||||
if ($entity->getEntityType()->hasLinkTemplate('canonical')) {
|
||||
redirect_delete_by_path('internal:/' . $entity->toUrl('canonical')->getInternalPath());
|
||||
redirect_delete_by_path('entity:' . $entity->getEntityTypeId() . '/' . $entity->id());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_path_update().
|
||||
*
|
||||
* Will create redirect from the old path alias to the new one.
|
||||
*/
|
||||
function redirect_path_update(array $path) {
|
||||
if (!\Drupal::config('redirect.settings')->get('auto_redirect')) {
|
||||
return;
|
||||
}
|
||||
$original_path = $path['original'];
|
||||
|
||||
// Delete all redirects having the same source as this alias.
|
||||
redirect_delete_by_path($path['alias'], $path['langcode'], FALSE);
|
||||
if ($original_path['alias'] != $path['alias']) {
|
||||
if (!redirect_repository()->findMatchingRedirect($original_path['alias'], array(), $original_path['langcode'])) {
|
||||
$redirect = Redirect::create();
|
||||
$redirect->setSource($original_path['alias']);
|
||||
$redirect->setRedirect($path['source']);
|
||||
$redirect->setLanguage($original_path['langcode']);
|
||||
$redirect->setStatusCode(\Drupal::config('redirect.settings')->get('default_status_code'));
|
||||
$redirect->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_path_insert().
|
||||
*/
|
||||
function redirect_path_insert(array $path) {
|
||||
// Delete all redirects having the same source as this alias.
|
||||
redirect_delete_by_path($path['alias'], $path['langcode'], FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_path_delete().
|
||||
*/
|
||||
function redirect_path_delete($path) {
|
||||
if (!\Drupal::config('redirect.settings')->get('auto_redirect')) {
|
||||
return;
|
||||
}
|
||||
elseif (isset($path['redirect']) && !$path['redirect']) {
|
||||
return;
|
||||
}
|
||||
elseif (empty($path)) {
|
||||
// @todo Remove this condition and allow $path to use an array type hint
|
||||
// when http://drupal.org/node/1025904 is fixed.
|
||||
return;
|
||||
}
|
||||
|
||||
// Redirect from a deleted alias to the system path.
|
||||
//if (!redirect_load_by_source($path['alias'], $path['language'])) {
|
||||
// $redirect = new stdClass();
|
||||
// redirect_create($redirect);
|
||||
// $redirect->source = $path['alias'];
|
||||
// $redirect->redirect = $path['source'];
|
||||
// $redirect->language = $path['language'];
|
||||
// redirect_save($redirect);
|
||||
//}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_page_build().
|
||||
*
|
||||
* Adds an action on 404 pages to create a redirect.
|
||||
*
|
||||
* @todo hook_page_build() can no longer be used for this. Find a different way.
|
||||
*/
|
||||
function redirect_page_build(&$page) {
|
||||
if (redirect_is_current_page_404() && \Drupal::currentUser()->hasPermission('administer redirects')) {
|
||||
if (!isset($page['content']['system_main']['actions'])) {
|
||||
$page['content']['system_main']['actions'] = array(
|
||||
'#theme' => 'links',
|
||||
'#links' => array(),
|
||||
'#attributes' => array('class' => array('action-links')),
|
||||
'#weight' => -100,
|
||||
);
|
||||
}
|
||||
// We cannot simply use current_path() because if a 404 path is set, then
|
||||
// that value overrides whatever is in $_GET['q']. The
|
||||
// drupal_deliver_html_page() function thankfully puts the original current
|
||||
// path into $_GET['destination'].
|
||||
$destination = drupal_get_destination();
|
||||
$page['content']['system_main']['actions']['#links']['add_redirect'] = array(
|
||||
'title' => t('Add URL redirect from this page to another location'),
|
||||
'href' => 'admin/config/search/redirect/add',
|
||||
'query' => array('source' => $destination['destination']) + drupal_get_destination(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the redirect repository service.
|
||||
*
|
||||
* @return \Drupal\redirect\RedirectRepository
|
||||
* The repository service.
|
||||
*/
|
||||
function redirect_repository() {
|
||||
return \Drupal::service('redirect.repository');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete any redirects associated with a path or any of its sub-paths.
|
||||
*
|
||||
* Given a source like 'node/1' this function will delete any redirects that
|
||||
* have that specific source or any sources that match 'node/1/%'.
|
||||
*
|
||||
* @param string $path
|
||||
* An string with an internal Drupal path.
|
||||
* @param string $langcode
|
||||
* (optional) If specified, limits deletion to redirects for the given
|
||||
* language. Defaults to all languages.
|
||||
* @param bool $match_subpaths_and_redirect
|
||||
* (optional) Whether redirects with a destination to the given path and
|
||||
* sub-paths should also be deleted.
|
||||
*
|
||||
* @ingroup redirect_api
|
||||
*/
|
||||
function redirect_delete_by_path($path, $langcode = NULL, $match_subpaths_and_redirect = TRUE) {
|
||||
$path = ltrim($path, '/');
|
||||
$database = \Drupal::database();
|
||||
$query = $database->select('redirect');
|
||||
$query->addField('redirect', 'rid');
|
||||
$query_or = db_or();
|
||||
$query_or->condition('redirect_source__path', $database->escapeLike($path), 'LIKE');
|
||||
if ($match_subpaths_and_redirect) {
|
||||
$query_or->condition('redirect_source__path', $database->escapeLike($path . '/') . '%', 'LIKE');
|
||||
$query_or->condition('redirect_redirect__uri', $database->escapeLike($path), 'LIKE');
|
||||
$query_or->condition('redirect_redirect__uri', $database->escapeLike($path . '/') . '%', 'LIKE');
|
||||
}
|
||||
|
||||
if ($langcode) {
|
||||
$query->condition('language', $langcode);
|
||||
}
|
||||
|
||||
$query->condition($query_or);
|
||||
$rids = $query->execute()->fetchCol();
|
||||
|
||||
if ($rids) {
|
||||
foreach (redirect_repository()->loadMultiple($rids) as $redirect) {
|
||||
$redirect->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort an array recusively.
|
||||
*
|
||||
* @param $array
|
||||
* The array to sort, by reference.
|
||||
* @param $callback
|
||||
* The sorting callback to use (e.g. 'sort', 'ksort', 'asort').
|
||||
*
|
||||
* @return
|
||||
* TRUE on success or FALSE on failure.
|
||||
*/
|
||||
function redirect_sort_recursive(&$array, $callback = 'sort') {
|
||||
$result = $callback($array);
|
||||
foreach ($array as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$result &= redirect_sort_recursive($array[$key], $callback);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the URL of a redirect for display purposes only.
|
||||
*/
|
||||
function redirect_url($path, array $options = array(), $clean_url = NULL) {
|
||||
// @todo - deal with removal of clean_url config. See
|
||||
// https://drupal.org/node/1659580
|
||||
if (!isset($clean_url)) {
|
||||
//$clean_url = variable_get('clean_url', 0);
|
||||
}
|
||||
|
||||
if ($path == '') {
|
||||
$path = '<front>';
|
||||
}
|
||||
|
||||
if (!isset($options['alter']) || !empty($options['alter'])) {
|
||||
\Drupal::moduleHandler()->alter('redirect_url', $path, $options);
|
||||
}
|
||||
|
||||
// The base_url might be rewritten from the language rewrite in domain mode.
|
||||
if (!isset($options['base_url'])) {
|
||||
// @todo - is this correct? See https://drupal.org/node/1798832.
|
||||
if (isset($options['https']) && Settings::get('mixed_mode_sessions', FALSE)) {
|
||||
if ($options['https'] === TRUE) {
|
||||
$options['base_url'] = $GLOBALS['base_secure_url'];
|
||||
$options['absolute'] = TRUE;
|
||||
}
|
||||
elseif ($options['https'] === FALSE) {
|
||||
$options['base_url'] = $GLOBALS['base_insecure_url'];
|
||||
$options['absolute'] = TRUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$options['base_url'] = $GLOBALS['base_url'];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($options['absolute']) || url_is_external($path)) {
|
||||
$url = $path;
|
||||
}
|
||||
else {
|
||||
$url = $options['base_url'] . base_path() . $path;
|
||||
}
|
||||
|
||||
if (isset($options['query'])) {
|
||||
$url .= $clean_url ? '?' : '&';
|
||||
$url .= UrlHelper::buildQuery($options['query']);
|
||||
}
|
||||
if (isset($options['fragment'])) {
|
||||
$url .= '#' . $options['fragment'];
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
function redirect_status_code_options($code = NULL) {
|
||||
$codes = array(
|
||||
300 => t('300 Multiple Choices'),
|
||||
301 => t('301 Moved Permanently'),
|
||||
302 => t('302 Found'),
|
||||
303 => t('303 See Other'),
|
||||
304 => t('304 Not Modified'),
|
||||
305 => t('305 Use Proxy'),
|
||||
307 => t('307 Temporary Redirect'),
|
||||
);
|
||||
return isset($codes[$code]) ? $codes[$code] : $codes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the current page request is a page not found (404 status error).
|
||||
*
|
||||
* Why the fuck do we have to do this? Why is there not an easier way???
|
||||
*
|
||||
* @return
|
||||
* TRUE if the current page is a 404, or FALSE otherwise.
|
||||
*/
|
||||
function redirect_is_current_page_404() {
|
||||
return drupal_get_http_header('Status') == '404 Not Found';
|
||||
}
|
||||
|
||||
/**
|
||||
* uasort callback; Compare redirects based on language neutrality and rids.
|
||||
*/
|
||||
function _redirect_uasort($a, $b) {
|
||||
$a_weight = isset($a->weight) ? $a->weight : 0;
|
||||
$b_weight = isset($b->weight) ? $b->weight : 0;
|
||||
if ($a_weight != $b_weight) {
|
||||
// First sort by weight (case sensitivity).
|
||||
return $a_weight > $b_weight;
|
||||
}
|
||||
elseif ($a->language != $b->language) {
|
||||
// Then sort by language specific over language neutral.
|
||||
return $a->language == Language::LANGCODE_NOT_SPECIFIED;
|
||||
}
|
||||
elseif (!empty($a->source_options['query']) != !empty($b->source_options['query'])) {
|
||||
// Then sort by redirects that do not have query strings over ones that do.
|
||||
return empty($a->source_options['query']);
|
||||
}
|
||||
else {
|
||||
// Lastly sort by the highest redirect ID.
|
||||
return $a->rid < $b->rid;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter() on behalf of locale.module.
|
||||
*/
|
||||
function locale_form_redirect_edit_form_alter(array &$form, FormStateInterface $form_state) {
|
||||
$form['language'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Language'),
|
||||
'#options' => array(Language::LANGCODE_NOT_SPECIFIED => t('All languages')) + \Drupal::languageManager()->getLanguages(),
|
||||
'#default_value' => $form['language']['#value'],
|
||||
'#description' => t('A redirect set for a specific language will always be used when requesting this page in that language, and takes precedence over redirects set for <em>All languages</em>.'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch an array of redirect bulk operations.
|
||||
*
|
||||
* @see hook_redirect_operations()
|
||||
* @see hook_redirect_operations_alter()
|
||||
*/
|
||||
function redirect_get_redirect_operations() {
|
||||
$operations = &drupal_static(__FUNCTION__);
|
||||
|
||||
if (!isset($operations)) {
|
||||
$operations = \Drupal::moduleHandler()->invokeAll('redirect_operations');
|
||||
\Drupal::moduleHandler()->alter('redirect_operations', $operations);
|
||||
}
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_redirect_operations().
|
||||
*/
|
||||
function redirect_redirect_operations() {
|
||||
$operations['delete'] = array(
|
||||
'action' => t('Delete'),
|
||||
'action_past' => t('Deleted'),
|
||||
'callback' => 'redirect_delete_multiple',
|
||||
'confirm' => TRUE,
|
||||
);
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax callback for the redirect link widget.
|
||||
*/
|
||||
function redirect_source_link_get_status_messages(array $form, FormStateInterface $form_state) {
|
||||
return $form['redirect_source']['widget'][0]['status_box'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_extra_field_info().
|
||||
*/
|
||||
function redirect_entity_extra_field_info() {
|
||||
$extra = [];
|
||||
|
||||
if (\Drupal::service('module_handler')->moduleExists('node')) {
|
||||
$node_types = \Drupal::entityTypeManager()
|
||||
->getStorage('node_type')
|
||||
->loadMultiple();
|
||||
|
||||
foreach ($node_types as $node_type) {
|
||||
$extra['node'][$node_type->id()]['form']['url_redirects'] = [
|
||||
'label' => t('URL redirects'),
|
||||
'description' => t('Redirect module form elements'),
|
||||
'weight' => 50,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_node_form_alter().
|
||||
*/
|
||||
function redirect_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
$node = $form_state->getFormObject()->getEntity();
|
||||
if (!$node->isNew() && \Drupal::currentUser()->hasPermission('administer redirects')) {
|
||||
|
||||
$nid = $node->id();
|
||||
|
||||
// Find redirects to this node.
|
||||
$redirects = \Drupal::service('redirect.repository')
|
||||
->findByDestinationUri(["internal:/node/$nid", "entity:node/$nid"]);
|
||||
|
||||
// Assemble the rows for the table.
|
||||
$rows = [];
|
||||
/** @var \Drupal\Core\Entity\EntityListBuilder $list_builder */
|
||||
$list_builder = \Drupal::service('entity.manager')->getListBuilder('redirect');
|
||||
/** @var \Drupal\redirect\Entity\Redirect[] $redirects */
|
||||
foreach ($redirects as $redirect) {
|
||||
$row = [];
|
||||
$path = $redirect->getSourcePathWithQuery();
|
||||
$row['path'] = [
|
||||
'class' => ['redirect-table__path'],
|
||||
'data' => ['#plain_text' => $path],
|
||||
'title' => $path,
|
||||
];
|
||||
$row['operations'] = [
|
||||
'data' => [
|
||||
'#type' => 'operations',
|
||||
'#links' => $list_builder->getOperations($redirect),
|
||||
],
|
||||
];
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
// Add the list to the vertical tabs section of the form.
|
||||
$header = [
|
||||
['class' => ['redirect-table__path'], 'data' => t('From')],
|
||||
['class' => ['redirect-table__operations'], 'data' => t('Operations')],
|
||||
];
|
||||
$form['url_redirects'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => t('URL redirects'),
|
||||
'#group' => 'advanced',
|
||||
'#open' => FALSE,
|
||||
'table' => [
|
||||
'#type' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $rows,
|
||||
'#empty' => t('No URL redirects available.'),
|
||||
'#attributes' => ['class' => ['redirect-table']],
|
||||
],
|
||||
'#attached' => [
|
||||
'library' => [
|
||||
'redirect/drupal.redirect.admin',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if (!empty($rows)) {
|
||||
$form['url_redirects']['warning'] = [
|
||||
'#markup' => t('Note: links open in the current window.'),
|
||||
'#prefix' => '<p>',
|
||||
'#suffix' => '</p>',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
administer redirects:
|
||||
title: 'Administer individual URL redirections'
|
||||
administer redirect settings:
|
||||
title: 'Administer global URL redirection settings'
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user