Commit 81676d3b by Ajay Barthwal

Merge branch 'feature-theme' into 'develop'

Feature theme

See merge request manzarH/dic-global-dev!9
parents 2b429879 f00f5842
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.
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Easy Breadcrumb module provides configurable breadcrumbs that improve on
core breadcrumbs by including the current page title as an unlinked crumb which
follows breadcrumb best-practices
(URL "https://www.nngroup.com/articles/breadcrumb-navigation-useful/").
Easy Breadcrumb takes advantage of the work you've already done for generating
your path aliases, while it naturally encourages the creation of semantic
and consistent paths. This module is currently available for Drupal 6.x, 7.x,
and 8.x.x.
Easy Breadcrumb uses the current URL (path alias) and the current page's title
to automatically extract the breadcrumb's segments and its respective links.
The module is really a plug and play module because it auto-generates the
breadcrumb by using the current URL and nothing extra is needed.
* For a full description of the module visit:
https://www.drupal.org/project/easy_breadcrumb
or
https://www.drupal.org/docs/8/improve-the-breadcrumbs
* To submit bug reports and feature suggestions, or to track changes visit:
https://www.drupal.org/node/2929013
REQUIREMENTS
------------
This module requires no modules outside of Drupal core.
INSTALLATION
------------
Install the Easy Breadcrumb module as you would normally install a contributed
Drupal module. Visit https://www.drupal.org/node/1897420 for further
information.
CONFIGURATION
-------------
1. Navigate to Administration > Extend and enable the module. The system
breadcrumb block has now been updated.
2. Navigate to Administration > Configuration > User Interface > Easy
Breadcrumb for configurations. Save Configurations.
Configurable parameters:
* Include / Exclude the front page as a segment in the breadcrumb.
* Include / Exclude the current page as the last segment in the breadcrumb.
* Use the real page title when it is available instead of always deducing it
from the URL.
* Print the page's title segment as a link.
* Make the language path prefix a segment on multilingual sites where a path
prefix ("/en") is used.
* Use menu title as fallback instead of raw path component.
* Remove segments of the breadcrumb that are identical.
* Use a custom separator between the breadcrumb's segments. (TODO)
* Choose a transformation mode for the segments' title.
* Make the 'capitalizator' ignore some words.
MAINTAINERS
-----------
* Greg Boggs - https://www.drupal.org/u/greg-boggs
* Neslee Canil Pinto - https://www.drupal.org/u/neslee-canil-pinto
* Jeff Mahoney (loopduplicate) - https://www.drupal.org/u/loopduplicate
Supporting organization:
* Kanopi Studios - https://www.drupal.org/kanopi-studios
{
"name": "drupal/easy_breadcrumb",
"type": "drupal-module",
"description": "Adds configuration to the system breadcrumbs.",
"homepage": "https://www.drupal.org/project/easy_breadcrumb",
"license": "GPL-2.0-or-later",
"authors": [
{
"name": "Neslee Canil Pinto",
"homepage": "https://www.drupal.org/u/neslee-canil-pinto",
"role": "Maintainer"
},
{
"name": "Greg Boggs",
"homepage": "https://www.drupal.org/u/greg-boggs",
"role": "Maintainer"
}
],
"support": {
"issues": "https://www.drupal.org/project/issues/easy_breadcrumb",
"source": "https://git.drupalcode.org/project/easy_breadcrumb"
},
"require": {
"drupal/core": "^8 || ^9"
}
}
applies_admin_routes: TRUE
include_home_segment: TRUE
home_segment_title: 'Home'
home_segment_keep: FALSE
include_title_segment: TRUE
language_path_prefix_as_segment: FALSE
use_menu_title_as_fallback: FALSE
use_page_title_as_menu_title_fallback: FALSE
remove_repeated_segments: TRUE
term_hierarchy: FALSE
absolute_paths: FALSE
hide_single_home_item: FALSE
title_from_page_when_available: TRUE
capitalizator_mode: 'ucwords'
capitalizator_ignored_words:
- of
- and
- or
- de
- del
- y
- o
- a
capitalizator_forced_words_first_letter: FALSE
capitalizator_forced_words_case_sensitivity: TRUE
add_structured_data_jsonld: FALSE
use_site_title: FALSE
dependencies:
module:
- easy_breadcrumb
enforced:
module:
- easy_breadcrumb
easy_breadcrumb.settings:
type: config_object
label: 'Easy Breadcrumb'
mapping:
applies_admin_routes:
type: boolean
label: 'Applies to administration pages'
include_invalid_paths:
type: boolean
label: 'Include invalid paths alias as plain-text segments'
excluded_paths:
type: text
label: 'Paths to be excluded while generating segments'
replaced_titles:
type: text
label: 'Titles to be replaced with synonyms'
custom_paths:
type: text
label: 'Paths requiring custom breadcrumbs'
include_home_segment:
type: boolean
label: 'Include the front page as a segment in the breadcrumb'
home_segment_title:
type: label
label: 'Title for the front page segment in the breadcrumb'
translatable: true
home_segment_keep:
type: boolean
label: 'Display the front page segment on the front page'
include_title_segment:
type: boolean
label: 'Include the current page as a segment in the breadcrumb'
title_from_page_when_available:
type: boolean
label: 'Use the real page title when available'
title_segment_as_link:
type: boolean
label: 'Make the page title segment a link'
segments_separator:
type: text
label: 'Segments Separator'
use_menu_title_as_fallback:
type: boolean
label: 'Use menu title as fallback instead of raw path component'
use_page_title_as_menu_title_fallback:
type: boolean
label: 'Use the page title as a fallback if the menu title is not set'
remove_repeated_segments:
type: boolean
label: 'Remove repeated identical segments'
language_path_prefix_as_segment:
type: boolean
label: 'Use language path prefix as segment'
absolute_paths:
type: boolean
label: 'Use absolute path for Breadcrumb links'
hide_single_home_item:
type: boolean
label: 'Hide the breadcrumb when it only links to the home page and nothing more'
term_hierarchy:
type: boolean
label: 'Use term hierarchy'
add_structured_data_jsonld:
type: boolean
label: 'Add current breadcrumb as structured data in JSON-LD to the HTML head'
use_site_title:
type: boolean
label: 'Use site title for the front page segment in the breadcrumb'
easy_breadcrumb.general_settings_form:
title: 'Easy Breadcrumb'
base_route_name: easy_breadcrumb.general_settings_form
names:
- easy_breadcrumb.settings
name: 'Easy Breadcrumb'
type: module
description: 'Provides configurable path based breadcrumbs.'
core: 8.x
core_version_requirement: ^8 || ^9
configure: easy_breadcrumb.general_settings_form
# Information added by Drupal.org packaging script on 2020-07-22
version: '8.x-1.13'
project: 'easy_breadcrumb'
datestamp: 1595377204
<?php
/**
* @file
* Add installation messages to help users get started and update.
*/
use Drupal\easy_breadcrumb\EasyBreadcrumbConstants;
/**
* Implements hook_update().
*
* Force an update because we removed an unused service.
*/
function easy_breadcrumb_update_8001() {
// Do nothing because database updates run cache rebuild.
}
/**
* Implements hook_update().
*
* Update home_segment_keep config from integer to boolean.
*/
function easy_breadcrumb_update_8002() {
$config = \Drupal::configFactory()->getEditable(EasyBreadcrumbConstants::MODULE_SETTINGS);
$home_segment_keep = $config->get(EasyBreadcrumbConstants::HOME_SEGMENT_KEEP);
// If the setting is enabled, store it as TRUE.
if ($home_segment_keep === 1) {
$config->set(EasyBreadcrumbConstants::HOME_SEGMENT_KEEP, TRUE);
}
// Otherwise, store it as FALSE.
else {
$config->set(EasyBreadcrumbConstants::HOME_SEGMENT_KEEP, FALSE);
}
$config->save();
}
/**
* Implements hook_update().
*
* Force an update because we updated the depedency injection which requires a
* cache clear.
*/
function easy_breadcrumb_update_8003() {
// Do nothing because database updates run cache rebuild.
}
/**
* Implements hook_update().
*
* Force a cache update because we changed service dependencies.
*/
function easy_breadcrumb_update_8004() {
// Do nothing because database updates run cache rebuild.
}
/**
* Force cache rebuild for newly added service to be picked up.
*/
function easy_breadcrumb_update_8005() {
drupal_flush_all_caches();
}
/**
* Implements hook_install().
*
* Help users get started with the module.
*/
function easy_breadcrumb_install() {
$messenger = \Drupal::messenger();
$help_url = 'https://www.drupal.org/docs/8/modules/easy-breadcrumb';
$messenger->addMessage("Easy Breadcrumb documentation is found in the help module or at $help_url.");
// Clear the cache so that the breadcrumbs appear after install.
drupal_flush_all_caches();
}
easy_breadcrumb.general_settings_form:
route_name: easy_breadcrumb.general_settings_form
title: 'Easy breadcrumb'
description: 'Controls settings for the module Easy Breadcrumb'
parent: system.admin_config_ui
easy_breadcrumb.general_settings_form_tab:
route_name: easy_breadcrumb.general_settings_form
title: Settings
base_route: easy_breadcrumb.general_settings_form
<?php
/**
* @file
* The Easy Breadcrumb module improves the core system breadcrumbs.
*/
use Drupal\Core\Block\BlockPluginInterface;
/**
* Implements hook_help().
*/
function easy_breadcrumb_help($route_name) {
switch ($route_name) {
case 'help.page.easy_breadcrumb':
$text = file_get_contents(__DIR__ . "/README.md");
// If the Markdown module is installed...
if (\Drupal::moduleHandler()->moduleExists('markdown') === TRUE) {
// Uses the Markdown filter to render the README.
$filter_manager = \Drupal::service('plugin.manager.filter');
$settings = \Drupal::configFactory()->get('markdown.settings')->getRawData();
$config = ['settings' => $settings];
$filter = $filter_manager->createInstance('markdown', $config);
$output = $filter->process($text, 'en');
}
// Else the Markdown module is not installed...
else {
// Outputs the README in plain text.
$output = '<pre>' . $text . '</pre>';
}
// Adds a link to the Drupal.org documentation pages.
$output .= t('<p>See the <a href=":documentation">documentation pages</a> on Drupal.org for more information.</p>',
[
':documentation' => 'https://www.drupal.org/docs/8/improve-the-breadcrumbs',
]);
return $output;
}
}
/**
* Implements hook_block_view_BASE_BLOCK_ID_alter().
*/
function easy_breadcrumb_block_view_system_breadcrumb_block_alter(array &$build, BlockPluginInterface $block) {
// Get JSON-LD.
if ($json_ld = \Drupal::service('easy_breadcrumb.structured_data_json_ld')
->value()) {
// Prepare script tag.
$structured_data = [
'#tag' => 'script',
'#attributes' => ['type' => 'application/ld+json'],
'#value' => $json_ld,
];
// Add script tag.
$build['#attached']['html_head'][] = [
$structured_data,
'easy_breadcrumb_structured_data_json_ld',
];
}
}
administer easy breadcrumb:
title: 'Administer Easy Breadcrumb settings'
easy_breadcrumb.general_settings_form:
path: /admin/config/user-interface/easy-breadcrumb
defaults:
_title: 'Easy Breadcrumb'
_form: \Drupal\easy_breadcrumb\Form\EasyBreadcrumbGeneralSettingsForm
requirements:
_permission: 'administer easy breadcrumb'
services:
easy_breadcrumb.breadcrumb:
class: Drupal\easy_breadcrumb\EasyBreadcrumbBuilder
arguments: ['@router.request_context', '@access_manager', '@router', '@request_stack', '@path_processor_manager', '@config.factory', '@title_resolver', '@current_user', '@path.current', '@plugin.manager.menu.link', '@language_manager', '@entity_type.manager', '@entity.repository', '@logger.factory', '@messenger', '@module_handler']
tags:
- { name: breadcrumb_builder, priority: 1003 }
easy_breadcrumb.structured_data_json_ld:
class: Drupal\easy_breadcrumb\EasyBreadcrumbStructuredDataJsonLd
arguments: ['@easy_breadcrumb.breadcrumb', '@config.factory', '@current_route_match']
<?php
namespace Drupal\easy_breadcrumb;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Controller\TitleResolverInterface;
use Drupal\Core\Extension\ModuleHandler;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Menu\MenuLinkManager;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\Routing\RequestContext;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Component\Utility\UrlHelper;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
/**
* Primary implementation for the Easy Breadcrumb builder.
*/
class EasyBreadcrumbBuilder implements BreadcrumbBuilderInterface {
use StringTranslationTrait;
/**
* The router request context.
*
* @var \Drupal\Core\Routing\RequestContext
*/
protected $context;
/**
* The access manager service.
*
* @var \Drupal\Core\Access\AccessManagerInterface
*/
protected $accessManager;
/**
* The request stack service.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The dynamic router service.
*
* @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
*/
protected $router;
/**
* The path processor service.
*
* @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
*/
protected $pathProcessor;
/**
* Site config object.
*
* @var \Drupal\Core\Config\Config
*/
protected $siteConfig;
/**
* Breadcrumb config object.
*
* @var \Drupal\Core\Config\Config
*/
protected $config;
/**
* The title resolver.
*
* @var \Drupal\Core\Controller\TitleResolverInterface
*/
protected $titleResolver;
/**
* The current user object.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The current path object.
*
* @var \Drupal\Core\Path\CurrentPathStack
*/
protected $currentPath;
/**
* The menu link manager.
*
* @var \Drupal\Core\Menu\MenuLinkManager
*/
protected $menuLinkManager;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The logger service.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected $logger;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity repository.
*
* @var \Drupal\Core\Entity\EntityRepositoryInterface
*/
protected $entityRepository;
/**
* The messenger service.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandler
*/
protected $moduleHandler;
/**
* Constructs the EasyBreadcrumbBuilder.
*
* @param \Drupal\Core\Routing\RequestContext $context
* The router request context.
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
* The access manager service.
* @param \Symfony\Component\Routing\Matcher\RequestMatcherInterface $router
* The dynamic router service.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack service.
* @param \Drupal\Core\PathProcessor\InboundPathProcessorInterface $path_processor
* The inbound path processor.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
* The title resolver service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user object.
* @param \Drupal\Core\Path\CurrentPathStack $current_path
* The current path.
* @param \Drupal\Core\Menu\MenuLinkManager $menu_link_manager
* The menu link manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger
* The logger service.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger service.
* @param \Drupal\Core\Extension\ModuleHandler $module_handler
* The module handler.
*/
public function __construct(RequestContext $context, AccessManagerInterface $access_manager, RequestMatcherInterface $router, RequestStack $request_stack, InboundPathProcessorInterface $path_processor, ConfigFactoryInterface $config_factory, TitleResolverInterface $title_resolver, AccountInterface $current_user, CurrentPathStack $current_path, MenuLinkManager $menu_link_manager, LanguageManagerInterface $language_manager, EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository, LoggerChannelFactoryInterface $logger, MessengerInterface $messenger, ModuleHandler $module_handler) {
$this->context = $context;
$this->accessManager = $access_manager;
$this->router = $router;
$this->requestStack = $request_stack;
$this->pathProcessor = $path_processor;
$this->siteConfig = $config_factory->get('system.site');
$this->config = $config_factory->get(EasyBreadcrumbConstants::MODULE_SETTINGS);
$this->titleResolver = $title_resolver;
$this->currentUser = $current_user;
$this->currentPath = $current_path;
$this->menuLinkManager = $menu_link_manager;
$this->languageManager = $language_manager;
$this->entityTypeManager = $entity_type_manager;
$this->entityRepository = $entity_repository;
$this->logger = $logger;
$this->messenger = $messenger;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public function applies(RouteMatchInterface $route_match) {
$applies_admin_routes = $this->config->get(EasyBreadcrumbConstants::APPLIES_ADMIN_ROUTES);
// If never set before ensure Applies to administration pages is on.
if (!isset($applies_admin_routes)) {
return TRUE;
}
$request = $this->requestStack->getCurrentRequest();
$route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT);
if ($route && $route->getOption('_admin_route') && $applies_admin_routes == FALSE) {
return FALSE;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function build(RouteMatchInterface $route_match) {
$breadcrumb = new Breadcrumb();
$links = [];
$exclude = [];
$curr_lang = $this->languageManager->getCurrentLanguage()->getId();
$replacedTitles = [];
$mapValues = preg_split('/[\r\n]+/', $this->config->get(EasyBreadcrumbConstants::REPLACED_TITLES));
foreach ($mapValues as $mapValue) {
$values = explode("::", $mapValue);
if (count($values) == 2) {
$replacedTitles[$values[0]] = $values[1];
}
}
// Set request context from the $route_match if route is available.
$this->setRouteContextFromRouteMatch($route_match);
// General path-based breadcrumbs. Use the actual request path, prior to
// resolving path aliases so the breadcrumb can be defined by creating a
// hierarchy of path aliases.
$path = trim($this->context->getPathInfo(), '/');
$path = urldecode($path);
$path_elements = explode('/', $path);
$front = $this->siteConfig->get('page.front');
// Give the option to keep the breadcrumb on the front page.
$keep_front = !empty($this->config->get(EasyBreadcrumbConstants::HOME_SEGMENT_TITLE))
&& $this->config->get(EasyBreadcrumbConstants::HOME_SEGMENT_KEEP);
$exclude[$front] = !$keep_front;
$exclude[''] = !$keep_front;
$exclude['/user'] = TRUE;
// See if we are doing a Custom Path override.
$path_crumb_row = preg_split('/[\r\n]+/', $this->config->get(EasyBreadcrumbConstants::CUSTOM_PATHS));
$path_crumb_row = array_filter($path_crumb_row);
foreach ($path_crumb_row as $path_crumb) {
$values = explode("::", $path_crumb);
// Shift path off array.
$custom_path = array_shift($values);
// Strip of leading/ending slashes and spaces/tabs (allows indenting
// rows on config page).
$custom_path = mb_strtolower(trim($custom_path, "/ \t"));
// Check if custom path includes the flag used to signify that the
// path is expressed as a regular expression pattern.
$regex_match = [];
$is_regex = preg_match('/^regex\s*!\s*\/(.*)/', $custom_path, $regex_match);
if ($is_regex) {
$custom_path = $regex_match[1];
$url_group_matches = [];
}
// If the path matches the current path, build the breadcrumbs.
if (
($is_regex && preg_match("|" . $custom_path . "|", $path, $url_group_matches))
|| (!$is_regex && $path == $custom_path)
) {
if ($this->config->get(EasyBreadcrumbConstants::INCLUDE_HOME_SEGMENT)) {
$links[] = Link::createFromRoute($this->config->get(EasyBreadcrumbConstants::HOME_SEGMENT_TITLE), '<front>');
}
// Get $title|[$url] pairs from $values.
foreach ($values as $pair) {
$settings = explode("|", $pair);
$title = Xss::filter(trim($settings[0]));
// Get URL if it is provided.
$url = '';
if (isset($settings[1])) {
$url = trim($settings[1]);
// If the custom path includes any regex match groups
// (eg. "/foo/(\d*)/bar") then check if the urls for any segments
// have matched group variables (eg. $1 or $3) and if they do
// substitute them out for the the corresponding
// matched strings.
if ($is_regex && count($url_group_matches) > 1) {
// Discard first element as that's the full matched string
// rather than a captured group.
array_shift($url_group_matches);
foreach ($url_group_matches as $group_num => $captured_str) {
$url = str_replace('$' . ($group_num + 1), urlencode($captured_str), $url);
}
}
// If URL is invalid, then display warning and disable the link.
if (!UrlHelper::isValid($url)) {
$this->messenger->addWarning($this->t("EasyBreadcrumb: Custom crumb for @path URL '@url' is invalid.", ['@path' => $path, '@url' => $url]));
$url = '';
}
// If URL is not start with slash then display warning
// and disable the link.
if ($url[0] != '/') {
$this->messenger->addWarning($this->t("EasyBreadcrumb: Custom crumb for @path URL '@url' should start with slash(/).", ['@path' => $path, '@url' => $url]));
$url = '';
}
}
if ($url) {
$links[] = new Link($title, Url::fromUserInput($url, ['absolute' => TRUE]));
}
else {
$links[] = Link::createFromRoute($title, '<none>');
}
}
// Handle views path expiration cache expiration.
$parameters = $route_match->getParameters();
foreach ($parameters as $key => $parameter) {
if ($key === 'view_id') {
$breadcrumb->addCacheTags(['config:views.view.' . $parameter]);
}
if ($parameter instanceof CacheableDependencyInterface) {
$breadcrumb->addCacheableDependency($parameter);
}
}
// Expire cache by languages and config changes.
$breadcrumb->addCacheContexts(['route', 'url.path', 'languages']);
// Expire cache context for config changes.
$breadcrumb->addCacheableDependency($this->config);
return $breadcrumb->setLinks($links);
}
}
// Handle views path expiration cache expiration.
$parameters = $route_match->getParameters();
foreach ($parameters as $key => $parameter) {
if ($key === 'view_id') {
$breadcrumb->addCacheTags(['config:views.view.' . $parameter]);
}
if ($parameter instanceof CacheableDependencyInterface) {
$breadcrumb->addCacheableDependency($parameter);
}
}
// Expire cache by languages and config changes.
$breadcrumb->addCacheContexts(['route', 'url.path', 'languages']);
$breadcrumb->addCacheableDependency($this->config);
$i = 0;
$add_langcode = FALSE;
// Remove the current page if it's not wanted.
if (!$this->config->get(EasyBreadcrumbConstants::INCLUDE_TITLE_SEGMENT)) {
array_pop($path_elements);
}
if (isset($path_elements[0])) {
// Remove the first parameter if it matches the current language.
if (!($this->config->get(EasyBreadcrumbConstants::LANGUAGE_PATH_PREFIX_AS_SEGMENT))) {
if (mb_strtolower($path_elements[0]) == $curr_lang) {
// Preserve case in language to allow path matching to work properly.
$curr_lang = $path_elements[0];
array_shift($path_elements);
$add_langcode = TRUE;
}
}
}
while (count($path_elements) > 0) {
$check_path = '/' . implode('/', $path_elements);
if ($add_langcode) {
$check_path = '/' . $curr_lang . $check_path;
}
// Copy the path elements for up-casting.
$route_request = $this->getRequestForPath($check_path, $exclude);
if ($this->config->get(EasyBreadcrumbConstants::EXCLUDED_PATHS)) {
$config_textarea = $this->config->get(EasyBreadcrumbConstants::EXCLUDED_PATHS);
$excludes = preg_split('/[\r\n]+/', $config_textarea, -1, PREG_SPLIT_NO_EMPTY);
if (in_array(end($path_elements), $excludes)) {
break;
}
}
if ($route_request) {
$route_match = RouteMatch::createFromRequest($route_request);
$access = $this->accessManager->check($route_match, $this->currentUser, NULL, TRUE);
$breadcrumb = $breadcrumb->addCacheableDependency($access);
// The set of breadcrumb links depends on the access result, so merge
// the access result's cacheability metadata.
if ($access->isAllowed()) {
if ($this->config->get(EasyBreadcrumbConstants::TITLE_FROM_PAGE_WHEN_AVAILABLE)) {
$title = $this->normalizeText($this->getTitleString($route_request, $route_match, $replacedTitles));
if (empty($title)) {
unset($title);
}
// If the title is to be replaced...
if (!empty($title) && array_key_exists($title, $replacedTitles)) {
// Replaces the title.
$title = $replacedTitles[(string) $title];
}
}
if (!isset($title)) {
if ($this->config->get(EasyBreadcrumbConstants::USE_MENU_TITLE_AS_FALLBACK)) {
// Try resolve the menu title from the route.
$route_name = $route_match->getRouteName();
$route_parameters = $route_match->getRawParameters()->all();
$menu_links = $this->menuLinkManager->loadLinksByRoute($route_name, $route_parameters);
if (empty($menu_links)) {
if ($this->config->get(EasyBreadcrumbConstants::USE_PAGE_TITLE_AS_MENU_TITLE_FALLBACK)) {
$title = $this->getTitleString($route_request, $route_match, $replacedTitles);
if ($title && array_key_exists($title, $replacedTitles)) {
$title = $replacedTitles[$title];
}
}
}
else {
$menu_link = reset($menu_links);
$title = $this->normalizeText($menu_link->getTitle());
if (array_key_exists($title, $replacedTitles)) {
$title = $replacedTitles[$title];
}
}
}
// Fallback to using the raw path component as the title if the
// route is missing a _title or _title_callback attribute.
if (!isset($title)) {
$title = $this->normalizeText(end($path_elements));
if (array_key_exists($title, $replacedTitles)) {
$title = $replacedTitles[$title];
}
}
}
// Add a linked breadcrumb unless it's the current page.
if ($i == 0
&& $this->config->get(EasyBreadcrumbConstants::INCLUDE_TITLE_SEGMENT)
&& !$this->config->get(EasyBreadcrumbConstants::TITLE_SEGMENT_AS_LINK)) {
$links[] = Link::createFromRoute($title, '<none>');
}
else {
$url = Url::fromRouteMatch($route_match);
if ($this->config->get(EasyBreadcrumbConstants::ABSOLUTE_PATHS)) {
$url->setOption('absolute', TRUE);
}
$links[] = new Link($title, $url);
}
// Add all term parents.
if ($i == 0
&& $this->config->get(EasyBreadcrumbConstants::TERM_HIERARCHY)
&& $term = $route_match->getParameter('taxonomy_term')) {
$parents = $this->entityTypeManager->getStorage('taxonomy_term')->loadAllParents($term->id());
// Unset current term.
array_shift($parents);
foreach ($parents as $parent) {
$parent = $this->entityRepository->getTranslationFromContext($parent);
$links[] = $parent->toLink();
}
}
unset($title);
$i++;
}
}
elseif ($this->config->get(EasyBreadcrumbConstants::INCLUDE_INVALID_PATHS) && empty($exclude[implode('/', $path_elements)])) {
$title = $this->normalizeText(end($path_elements));
$this->applyTitleReplacement($title, $replacedTitles);
$links[] = Link::createFromRoute($title, '<none>');
unset($title);
}
array_pop($path_elements);
}
// Add the home link, if desired.
if ($this->config->get(EasyBreadcrumbConstants::INCLUDE_HOME_SEGMENT)) {
if ($path && '/' . $path != $front && $path != $curr_lang) {
if (!$this->config->get(EasyBreadcrumbConstants::USE_SITE_TITLE)) {
$links[] = Link::createFromRoute($this->normalizeText($this->config->get(EasyBreadcrumbConstants::HOME_SEGMENT_TITLE)), '<front>');
}
else {
$links[] = Link::createFromRoute($this->siteConfig->get('name'), '<front>');
}
}
if ($this->config->get(EasyBreadcrumbConstants::HIDE_SINGLE_HOME_ITEM) && count($links) === 1) {
return $breadcrumb->setLinks([]);
}
}
$links = array_reverse($links);
if ($this->config->get(EasyBreadcrumbConstants::REMOVE_REPEATED_SEGMENTS)) {
$links = $this->removeRepeatedSegments($links);
}
return $breadcrumb->setLinks($links);
}
/**
* Set request context from passed in $route_match if route is available.
*
* @param Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match for the breadcrumb.
*/
protected function setRouteContextFromRouteMatch(RouteMatchInterface $route_match) {
try {
$url = $route_match->getRouteObject() ? Url::fromRouteMatch($route_match) : NULL;
if ($url && $request = $this->getRequestForPath($url->toString(), [])) {
$route_match_context = new RequestContext();
$route_match_context->fromRequest($request);
$this->context = $route_match_context;
}
}
catch (RouteNotFoundException $e) {
// Ignore the exception.
}
}
/**
* Apply title replacements.
*
* @param string $title
* Page title.
* @param array $replacements
* Replacement rules map.
*/
public function applyTitleReplacement(&$title, array $replacements) {
if (!is_string($title)) {
return;
}
if (array_key_exists($title, $replacements)) {
$title = $replacements[$title];
}
}
/**
* Get string title for route.
*
* @param \Symfony\Component\HttpFoundation\Request $route_request
* A request object.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* A RouteMatch object.
* @param array $replacedTitles
* A array replaced titles.
*
* @return string|null
* Either the current title string or NULL if unable to determine it.
*/
public function getTitleString(Request $route_request, RouteMatchInterface $route_match, array $replacedTitles) {
$title = $this->titleResolver->getTitle($route_request, $route_match->getRouteObject());
$this->applyTitleReplacement($title, $replacedTitles);
// If title is object then try to render it.
if ($title instanceof MarkupInterface) {
$title = strip_tags((string) $title);
}
// Other paths, such as admin/structure/menu/manage/main, will
// return a render array suitable to render using core's XSS filter.
elseif (is_array($title) && array_key_exists('#markup', $title)) {
// If this render array has #allowed tags use that instead of default.
$tags = array_key_exists('#allowed_tags', $title) ? $title['#allowed_tags'] : NULL;
$title = Xss::filter($title['#markup'], $tags);
}
// If a route declares the title in an unexpected way, log and return NULL.
if (!is_string($title)) {
$this->logger->get('easy_breadcrumb')->notice('Easy Breadcrumb could not determine the title to use for @path', ['@path' => $route_match->getRouteObject()->getPath()]);
return NULL;
}
return $title;
}
/**
* Remove duplicate repeated segments.
*
* @param \Drupal\Core\Link[] $links
* The links.
*
* @return \Drupal\Core\Link[]
* The new links.
*/
protected function removeRepeatedSegments(array $links) {
$newLinks = [];
/** @var \Drupal\Core\Link $last */
$last = NULL;
foreach ($links as $link) {
if (empty($last) || (!$this->linksAreEqual($last, $link))) {
$newLinks[] = $link;
}
$last = $link;
}
return $newLinks;
}
/**
* Compares two breadcrumb links for equality.
*
* @param \Drupal\Core\Link $link1
* The first link.
* @param \Drupal\Core\Link $link2
* The second link.
*
* @return bool
* TRUE if equal, FALSE otherwise.
*/
protected function linksAreEqual(Link $link1, Link $link2) {
$links_equal = TRUE;
if ($link1->getText() instanceof TranslatableMarkup) {
$link_one_text = (string) $link1->getText();
}
else {
$link_one_text = $link1->getText();
}
if ($link2->getText() instanceof TranslatableMarkup) {
$link_two_text = (string) $link2->getText();
}
else {
$link_two_text = $link2->getText();
}
if ($link_one_text != $link_two_text) {
$links_equal = FALSE;
}
if ($link1->getUrl()->getInternalPath() != $link2->getUrl()->getInternalPath()) {
$links_equal = FALSE;
}
return $links_equal;
}
/**
* Matches a path in the router.
*
* @param string $path
* The request path with a leading slash.
* @param array $exclude
* An array of paths or system paths to skip.
*
* @return \Symfony\Component\HttpFoundation\Request
* A populated request object or NULL if the path couldn't be matched.
*/
protected function getRequestForPath($path, array $exclude) {
if (!empty($exclude[$path])) {
return NULL;
}
// Check to see if the path is actually a redirect, if it is, resolve it to
// its source before we create the request. Strip the starting slash,
// redirect module doesn't include it.
if ($this->moduleHandler->moduleExists('redirect')) {
$redirect_path = $path;
if ($redirect_path[0] === '/') {
$redirect_path = substr($redirect_path, 1);
}
$language_prefix = $this->languageManager->getCurrentLanguage()->getId();
if (strpos($redirect_path, "$language_prefix/") === 0) {
$redirect_path = substr($redirect_path, strlen("$language_prefix/"));
}
$redirects = \Drupal::service('redirect.repository')
->findBySourcePath($redirect_path);
if (!empty($redirects)) {
// Take the first redirect if we have multiple, there should normally
// only be one redirect for a source.
/** @var \Drupal\redirect\Entity\Redirect $redirect */
$redirect = current($redirects);
$path = $redirect->getRedirectUrl()->toString();
}
}
// @todo Use the RequestHelper once https://www.drupal.org/node/2090293 is
// fixed.
$request = Request::create($path);
// Performance optimization: set a short accept header to reduce overhead in
// AcceptHeaderMatcher when matching the request.
$request->headers->set('Accept', 'text/html');
// Find the system path by resolving aliases, language prefix, etc.
$processed = $this->pathProcessor->processInbound($path, $request);
if (empty($processed) || !empty($exclude[$processed])) {
// This resolves to the front page, which we already add.
return NULL;
}
$this->currentPath->setPath($processed, $request);
// Attempt to match this path to provide a fully built request.
try {
$request->attributes->add($this->router->matchRequest($request));
return $request;
}
catch (ParamNotConvertedException $e) {
return NULL;
}
catch (ResourceNotFoundException $e) {
return NULL;
}
catch (MethodNotAllowedException $e) {
return NULL;
}
catch (AccessDeniedHttpException $e) {
return NULL;
}
}
/**
* Normalizes a text.
*
* E.g., transforms "about-us" to "About Us" or "About us", according to
* parameters.
*
* @param string $raw_text
* Text to be normalized.
*
* @return string
* Normalized title.
*/
private function normalizeText($raw_text) {
// Transform '-hello--world_javascript-' to 'hello world javascript'.
$normalized_text = str_replace(['-', '_'], ' ', $raw_text);
$normalized_text = trim($normalized_text);
$normalized_text = preg_replace('/\s{2,}/', ' ', $normalized_text);
// Gets the flag saying the capitalizator mode.
$capitalizator_mode = $this->config->get(EasyBreadcrumbConstants::CAPITALIZATOR_MODE);
if ($capitalizator_mode === 'ucwords') {
// Transforms the text 'once a time' to 'Once a Time'.
// List of words to be ignored by the capitalizator.
$ignored_words = $this->config->get(EasyBreadcrumbConstants::CAPITALIZATOR_IGNORED_WORDS);
$words = explode(' ', $normalized_text);
// Transforms the non-ignored words of the segment.
$words[0] = Unicode::ucfirst($words[0]);
$words_quantity = count($words);
for ($i = 1; $i < $words_quantity; ++$i) {
// Transforms this word only if it is not in the list of ignored words.
if (!isset($ignored_words[$words[$i]])) {
$words[$i] = Unicode::ucfirst($words[$i]);
}
}
$normalized_text = implode(' ', $words);
}
elseif ($capitalizator_mode === 'ucall') {
// Transforms the text 'once a time' to 'ONCE A TIME'.
$normalized_text = mb_strtoupper($normalized_text);
}
elseif ($capitalizator_mode === 'ucforce') {
// Transforms the text 'once a time' to 'once a TIME'.
// List of words to be forced by the capitalizator.
$forced_words = $this->config->get(EasyBreadcrumbConstants::CAPITALIZATOR_FORCED_WORDS);
// If case sensitivity is false make all the forced words
// uncapitalized by default.
if ($forced_words && !$this->config->get(EasyBreadcrumbConstants::CAPITALIZATOR_FORCED_WORDS_CASE_SENSITIVITY)) {
$forced_words = array_map('strtolower', $forced_words);
}
$words = explode(' ', $normalized_text);
// Transforms the non-ignored words of the segment.
if ($this->config->get(EasyBreadcrumbConstants::CAPITALIZATOR_FORCED_WORDS_FIRST_LETTER)) {
$words[0] = Unicode::ucfirst($words[0]);
}
$words_quantity = count($words);
for ($i = 0; $i < $words_quantity; ++$i) {
// If case sensitivity is false make the compared word uncapitalized in
// order to allow the comparison well.
if (!$this->config->get(EasyBreadcrumbConstants::CAPITALIZATOR_FORCED_WORDS_CASE_SENSITIVITY)) {
$selected_word = mb_strtolower($words[$i]);
}
else {
$selected_word = $words[$i];
}
// Transforms this word only if it is in the list of forced words.
if (is_array($forced_words) && in_array($selected_word, $forced_words)) {
$words[$i] = mb_strtoupper($selected_word);
}
}
$normalized_text = implode(' ', $words);
}
else {
// Transforms the text 'once a time' to 'Once a time' (ucfirst).
$normalized_text = Unicode::ucfirst($normalized_text);
}
return $normalized_text;
}
}
<?php
namespace Drupal\easy_breadcrumb;
/**
* EasyBreadcrumb module's contants.
*/
class EasyBreadcrumbConstants {
/**
* Module's name.
*/
const MODULE_NAME = 'easy_breadcrumb';
/**
* Module's settings.
*/
const MODULE_SETTINGS = 'easy_breadcrumb.settings';
/**
* Flag for applying easy breadcrumb to admin routes.
*/
const APPLIES_ADMIN_ROUTES = 'applies_admin_routes';
/**
* Flag for including invalid paths while generating the breadcrumb segments.
*/
const INCLUDE_INVALID_PATHS = 'include_invalid_paths';
/**
* List of paths to be excluded from the generated segments.
*/
const EXCLUDED_PATHS = 'excluded_paths';
/**
* List of titles to replace.
*/
const REPLACED_TITLES = 'replaced_titles';
/**
* List of paths for custom breadcrumbs.
*/
const CUSTOM_PATHS = 'custom_paths';
/**
* Separator between segments.
*/
const SEGMENTS_SEPARATOR = 'segments_separator';
/**
* Flag for including or not the front page as a segment.
*/
const INCLUDE_HOME_SEGMENT = 'include_home_segment';
/**
* Title for the front page segment.
*/
const HOME_SEGMENT_TITLE = 'home_segment_title';
/**
* Flag for keeping the breadcrumb on the front page.
*/
const HOME_SEGMENT_KEEP = 'home_segment_keep';
/**
* Flag for including or not the page's title as a segment.
*/
const INCLUDE_TITLE_SEGMENT = 'include_title_segment';
/**
* Flag for printing the page's title as a link, or printing it as a text.
*/
const TITLE_SEGMENT_AS_LINK = 'title_segment_as_link';
/**
* Use the page's title when it is available.
*/
const TITLE_FROM_PAGE_WHEN_AVAILABLE = 'title_from_page_when_available';
/**
* Transformation mode to apply to the segments.
*/
const CAPITALIZATOR_MODE = 'capitalizator_mode';
/**
* List of words to be ignored by the 'capitalizator'. E.g.: of and.
*/
const CAPITALIZATOR_IGNORED_WORDS = 'capitalizator_ignored_words';
/**
* List of words to be forced by the 'capitalizator'. E.g.: your brand's name.
*/
const CAPITALIZATOR_FORCED_WORDS = 'capitalizator_forced_words';
/**
* List of words to be forced by the 'capitalizator'. E.g.: your brand's name.
*/
const CAPITALIZATOR_FORCED_WORDS_FIRST_LETTER = 'capitalizator_forced_words_first_letter';
/**
* Logical value to 'Make the first letters of each segment capitalized'.
*/
const CAPITALIZATOR_FORCED_WORDS_CASE_SENSITIVITY = 'capitalizator_forced_words_case_sensitivity';
/**
* Flag for showing the language prefix as its own segment.
*/
const LANGUAGE_PATH_PREFIX_AS_SEGMENT = 'language_path_prefix_as_segment';
/**
* Use menu title as fallback.
*/
const USE_MENU_TITLE_AS_FALLBACK = 'use_menu_title_as_fallback';
/**
* Use page title as fallback for menu title.
*/
const USE_PAGE_TITLE_AS_MENU_TITLE_FALLBACK = 'use_page_title_as_menu_title_fallback';
/**
* Use site title as the front page segment.
*/
const USE_SITE_TITLE = 'use_site_title';
/**
* Flag for removing repeated identical segments from the breadcrumb.
*/
const REMOVE_REPEATED_SEGMENTS = 'remove_repeated_segments';
/**
* Flag for storing absolute path settings.
*/
const ABSOLUTE_PATHS = 'absolute_paths';
/**
* Flag for storing single home item settings.
*/
const HIDE_SINGLE_HOME_ITEM = 'hide_single_home_item';
/**
* Flag for using term hierarchy.
*/
const TERM_HIERARCHY = 'term_hierarchy';
/**
* Flag for adding the breadcrumb as structured to the HTML head.
*/
const ADD_STRUCTURED_DATA_JSON_LD = 'add_structured_data_json_ld';
/**
* Default list of excluded paths.
*
* @return array
* Default list of ignored paths.
*/
public static function defaultExcludedPaths() {
static $default_excluded_paths = [
'search',
'search/node',
];
return $default_excluded_paths;
}
/**
* Default list of replaced titles.
*
* @return array
* Default list of replaced titles.
*/
public static function defaultReplacedTitles() {
return [];
}
/**
* Default list of replaced paths.
*
* @return array
* Default list of replaced paths.
*/
public static function defaultCustomPaths() {
return [];
}
}
<?php
namespace Drupal\easy_breadcrumb;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class EasyBreadcrumbStructuredDataJsonLd.
*
* @package Drupal\easy_breadcrumb
*/
class EasyBreadcrumbStructuredDataJsonLd implements ContainerInjectionInterface {
/**
* The Easy Breadcrumb builder.
*
* @var \Drupal\easy_breadcrumb\EasyBreadcrumbBuilder
*/
protected $easyBreadcrumbBuilder;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The route match.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* EasyBreadcrumbStructuredDataJsonLd constructor.
*
* @param \Drupal\easy_breadcrumb\EasyBreadcrumbBuilder $easy_breadcrumb_builder
* The Easy Breadcrumb builder.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
*/
public function __construct(EasyBreadcrumbBuilder $easy_breadcrumb_builder, ConfigFactoryInterface $config_factory, RouteMatchInterface $route_match) {
$this->easyBreadcrumbBuilder = $easy_breadcrumb_builder;
$this->configFactory = $config_factory;
$this->routeMatch = $route_match;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('easy_breadcrumb.breadcrumb'),
$container->get('config.factory'),
$container->get('current_route_match')
);
}
/**
* Build JSON-LD value.
*/
public function value() {
$value = FALSE;
$config = $this->configFactory->get(EasyBreadcrumbConstants::MODULE_SETTINGS);
if ($config->get(EasyBreadcrumbConstants::ADD_STRUCTURED_DATA_JSON_LD)) {
/** @var \Drupal\Core\Breadcrumb\Breadcrumb $breadcrumb */
$breadcrumb = $this->easyBreadcrumbBuilder->build($this->routeMatch);
$links = $breadcrumb->getLinks();
// Only fire if at least one link present.
if (count($links) > 0) {
// Open JSON.
$value = '{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [';
$position = 1;
/** @var \Drupal\Core\Link $link */
foreach ($links as $link) {
$name = $link->getText();
$item = $link->getUrl()->setAbsolute(TRUE)->toString();
// Add a comma before each item except the first.
if ($position > 1) {
$value .= ',';
}
// Only add item if link's not empty.
if (!empty($item)) {
$value .= '{
"@type": "ListItem",
"position": "' . $position . '",
"name": "' . $name . '",
"item": "' . $item . '"
}';
}
else {
$value .= '{
"@type": "ListItem",
"position": "' . $position . '",
"name": "' . $name . '"
}';
}
// Increment position for next run.
$position++;
}
// Close JSON.
$value .= ']}';
}
}
return $value;
}
}
<?php
namespace Drupal\easy_breadcrumb\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\easy_breadcrumb\EasyBreadcrumbConstants;
/**
* Build Easy Breadcrumb settings form.
*/
class EasyBreadcrumbGeneralSettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'easy_breadcrumb_general_settings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [EasyBreadcrumbConstants::MODULE_SETTINGS];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config(EasyBreadcrumbConstants::MODULE_SETTINGS);
// Details for grouping general settings fields.
$details_general = [
'#type' => 'details',
'#title' => $this->t('General settings'),
'#open' => TRUE,
];
$details_advanced = [
'#type' => 'details',
'#title' => $this->t('Advanced settings'),
'#open' => TRUE,
];
// If never set before ensure Applies to administration pages is on.
$applies_admin_routes = $config->get(EasyBreadcrumbConstants::APPLIES_ADMIN_ROUTES);
if (!isset($applies_admin_routes)) {
$applies_admin_routes = TRUE;
}
$details_general[EasyBreadcrumbConstants::APPLIES_ADMIN_ROUTES] = [
'#type' => 'checkbox',
'#title' => $this->t('Applies to administration pages'),
'#description' => $this->t('Uncheck to disable Easy breadcrumb for administration pages and routes like this one.'),
'#default_value' => $applies_admin_routes,
];
$details_general[EasyBreadcrumbConstants::INCLUDE_INVALID_PATHS] = [
'#type' => 'checkbox',
'#title' => $this->t('Include invalid paths alias as plain-text segments'),
'#description' => $this->t('Include the invalid paths alias as plain-text segments in the breadcrumb.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::INCLUDE_INVALID_PATHS),
];
$details_general[EasyBreadcrumbConstants::INCLUDE_TITLE_SEGMENT] = [
'#type' => 'checkbox',
'#title' => $this->t('Include the current page as a segment in the breadcrumb'),
'#description' => $this->t('Include the current page as the last segment in the breadcrumb.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::INCLUDE_TITLE_SEGMENT),
];
$details_general[EasyBreadcrumbConstants::REMOVE_REPEATED_SEGMENTS] = [
'#type' => 'checkbox',
'#title' => $this->t('Remove repeated identical segments'),
'#description' => $this->t('Remove segments of the breadcrumb that are identical.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::REMOVE_REPEATED_SEGMENTS),
];
$details_general[EasyBreadcrumbConstants::INCLUDE_HOME_SEGMENT] = [
'#type' => 'checkbox',
'#title' => $this->t('Include the front page as a segment in the breadcrumb'),
'#description' => $this->t('Include the front page as the first segment in the breadcrumb.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::INCLUDE_HOME_SEGMENT),
];
$details_general[EasyBreadcrumbConstants::HOME_SEGMENT_TITLE] = [
'#type' => 'textfield',
'#title' => $this->t('Title for the front page segment in the breadcrumb'),
'#description' => $this->t('Text to be displayed as the front page segment. This field works together with the "Include the front page as a segment in the breadcrumb"-option.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::HOME_SEGMENT_TITLE),
];
$details_general[EasyBreadcrumbConstants::TITLE_FROM_PAGE_WHEN_AVAILABLE] = [
'#type' => 'checkbox',
'#title' => $this->t('Use the real page title when available'),
'#description' => $this->t('Use the real page title when it is available instead of always deducing it from the URL.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::TITLE_FROM_PAGE_WHEN_AVAILABLE),
];
$details_general[EasyBreadcrumbConstants::USE_MENU_TITLE_AS_FALLBACK] = [
'#type' => 'checkbox',
'#title' => $this->t('Use menu title when available'),
'#description' => $this->t('Use menu title instead of raw path component. The real page title setting above will take presidence over this setting. So, one or the other, but not both.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::USE_MENU_TITLE_AS_FALLBACK),
];
$details_general[EasyBreadcrumbConstants::USE_PAGE_TITLE_AS_MENU_TITLE_FALLBACK] = [
'#type' => 'checkbox',
'#title' => $this->t('Use page title as fallback for menu title'),
'#description' => $this->t('Use page title as fallback if menu title cannot be found. This option works when not using "real page title" above.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::USE_PAGE_TITLE_AS_MENU_TITLE_FALLBACK),
];
$details_general[EasyBreadcrumbConstants::USE_SITE_TITLE] = [
'#type' => 'checkbox',
'#title' => $this->t('Use site title as the front page segment'),
'#description' => $this->t('Use site title as the front page segment. This field works together with the "Include the front page as a segment in the breadcrumb"-option.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::USE_SITE_TITLE),
];
$details_general[EasyBreadcrumbConstants::ADD_STRUCTURED_DATA_JSON_LD] = [
'#type' => 'checkbox',
'#title' => $this->t('Add current breadcrumb as structured data.'),
'#description' => $this->t('Check to have the current breadcrumb trail added as <a href="@href" target="_blank">structured data</a> in JSON-LD to the HTML <code><head></code>.', ['@href' => 'https://developers.google.com/search/docs/data-types/breadcrumb']),
'#default_value' => $config->get(EasyBreadcrumbConstants::ADD_STRUCTURED_DATA_JSON_LD),
];
// Formats the excluded paths array as line separated list of paths
// before displaying them.
$excluded_paths = $config->get(EasyBreadcrumbConstants::EXCLUDED_PATHS);
$details_advanced[EasyBreadcrumbConstants::EXCLUDED_PATHS] = [
'#type' => 'textarea',
'#title' => $this->t('Paths to be excluded while generating segments'),
'#description' => $this->t('Enter a line separated list of paths to be excluded while generating the segments.
Paths may use simple regex, i.e.: report/2[0-9][0-9][0-9].'),
'#default_value' => $excluded_paths,
];
// Formats the excluded paths array as line separated list of paths
// before displaying them.
$replaced_titles = $config->get(EasyBreadcrumbConstants::REPLACED_TITLES);
$details_advanced[EasyBreadcrumbConstants::REPLACED_TITLES] = [
'#type' => 'textarea',
'#title' => $this->t('Titles to be replaced while generating segments'),
'#description' => $this->t('Enter a line separated list of titles with their replacements separated by ::.<br>
For example TITLE::DIFFERENT_TITLE<br>This field works together with the option "Use the real page title when available" option.'),
'#default_value' => $replaced_titles,
];
// Formats the custom paths array as line separated list of paths
// before displaying them.
$custom_paths = $config->get(EasyBreadcrumbConstants::CUSTOM_PATHS);
$details_advanced[EasyBreadcrumbConstants::CUSTOM_PATHS] = [
'#type' => 'textarea',
'#title' => $this->t('Paths to replace with custom breadcrumbs'),
'#description' => $this->t('Enter a line separated list of internal paths followed by breadcrumb pattern. Separate crumbs from their path with a vertical bar ("|"). Separate crumbs with double-colon ("::"). Omit the URL to display an unlinked crumb. Fields will be trimmed to remove extra start/end spaces, so you can use them to help format your input, if desired. Replaced Titles will not be processed on custom paths. Excluded paths listed here will have breadcrumbs added. Examples (with and without extra spacing):<br><code>/news/archive/site_launched :: News | /news :: Archive | /news/archive :: Site Launched<br>/your/path::LinkedCrumb1|url1::LinkedCrumb2|url2::UnlinkedCrumb3</code><br><p>It is also possible to express the path to be matched as a <a href="https://www.php.net/manual/en/book.pcre.php" target="_blank">regex expression</a>. "regex!" must be added to the start of the path to match in order for it to be interpreted as regex:<br><code>regex!/news/archive/\d{4} :: News | /news :: Archive | /news/archive</code><p>Expressions can even include matching groups which can be referenced in the path of a segment path:<br><code>regex!/groups/([^/]*)/info :: Groups | /groups :: Group | /groups/$1</code></p>'),
'#default_value' => $custom_paths,
];
$details_advanced[EasyBreadcrumbConstants::HOME_SEGMENT_KEEP] = [
'#type' => 'checkbox',
'#title' => $this->t('Display the front page segment on the front page'),
'#description' => $this->t('If checked, the Home segment will be displayed on the front page.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::HOME_SEGMENT_KEEP),
'#states' => [
'visible' => [
':input[name="' . EasyBreadcrumbConstants::HOME_SEGMENT_TITLE . '"]' => ['empty' => FALSE],
],
],
];
$details_advanced[EasyBreadcrumbConstants::TITLE_SEGMENT_AS_LINK] = [
'#type' => 'checkbox',
'#title' => $this->t('Make the current page title segment a link'),
'#description' => $this->t('Prints the page title segment as a link. This option works together with the "Include the current page as a segment in the breadcrumb"-option.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::TITLE_SEGMENT_AS_LINK),
];
$details_advanced[EasyBreadcrumbConstants::LANGUAGE_PATH_PREFIX_AS_SEGMENT] = [
'#type' => 'checkbox',
'#title' => $this->t('Make the language path prefix a segment'),
'#description' => $this->t('On multilingual sites where a path prefix ("/en") is used, add this in the breadcrumb.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::LANGUAGE_PATH_PREFIX_AS_SEGMENT),
];
$details_advanced[EasyBreadcrumbConstants::ABSOLUTE_PATHS] = [
'#type' => 'checkbox',
'#title' => $this->t('Use absolute path for Breadcrumb links'),
'#description' => $this->t('By selecting, absolute paths will be used (default: false) instead of relative.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::ABSOLUTE_PATHS),
];
$details_advanced[EasyBreadcrumbConstants::HIDE_SINGLE_HOME_ITEM] = [
'#type' => 'checkbox',
'#title' => $this->t("Hide link to home page if it's the only breadcrumb item"),
'#description' => $this->t('Hide the breadcrumb when it only links to the home page and nothing more. <br> <strong>Note: If the homepage path is a "/" then this feature has an uncertain behavior.</strong>'),
'#default_value' => $config->get(EasyBreadcrumbConstants::HIDE_SINGLE_HOME_ITEM),
];
$details_advanced[EasyBreadcrumbConstants::TERM_HIERARCHY] = [
'#type' => 'checkbox',
'#title' => $this->t('Add parent hierarchy'),
'#description' => $this->t('Add all taxonomy parents in the crumb for current term.'),
'#default_value' => $config->get(EasyBreadcrumbConstants::TERM_HIERARCHY),
];
$details_advanced[EasyBreadcrumbConstants::CAPITALIZATOR_MODE] = [
'#type' => 'select',
'#title' => $this->t("Transformation mode for the segments' titles"),
'#options' => [
'none' => $this->t('None'),
'ucwords' => $this->t("Capitalize the first letter of each word in the segment"),
'ucfirst' => $this->t("Only capitalize the first letter of each segment"),
'ucall' => $this->t("Capitalize all the letters of each word in the segment"),
'ucforce' => $this->t("Capitalize only the words that are set below"),
],
'#description' => $this->t("Choose the transformation mode you want to apply to the segments' titles. E.g.: 'blog/once-a-time' -> 'Home >> Blog >> Once a Time'."),
'#default_value' => $config->get(EasyBreadcrumbConstants::CAPITALIZATOR_MODE),
];
// Formats the ignored-words array as space separated list of words
// (word1 word2 wordN) before displaying them.
$capitalizator_ignored_words_arr = $config->get(EasyBreadcrumbConstants::CAPITALIZATOR_IGNORED_WORDS);
$capitalizator_ignored_words = @implode(' ', $capitalizator_ignored_words_arr);
$details_advanced[EasyBreadcrumbConstants::CAPITALIZATOR_IGNORED_WORDS] = [
'#type' => 'textarea',
'#rows' => 3,
'#title' => $this->t("Words to be ignored by the 'capitalizator'"),
'#description' => $this->t("Enter a space separated list of words to be ignored by the 'capitalizator'. This will be applied only to the words not at the beginning of each segment. E.g.: of and."),
'#default_value' => $capitalizator_ignored_words,
'#states' => [
'visible' => [
':input[name="' . EasyBreadcrumbConstants::CAPITALIZATOR_MODE . '"]' => ['value' => 'ucwords'],
],
'invisible' => [
':input[name="' . EasyBreadcrumbConstants::CAPITALIZATOR_MODE . '"]' => ['!value' => 'ucwords'],
],
],
];
// Formats the forced-words array as space separated list of words
// (word1 word2 wordN) before displaying them.
$capitalizator_forced_words_arr = $config->get(EasyBreadcrumbConstants::CAPITALIZATOR_FORCED_WORDS);
$capitalizator_forced_words = @implode(' ', $capitalizator_forced_words_arr);
$details_advanced[EasyBreadcrumbConstants::CAPITALIZATOR_FORCED_WORDS] = [
'#type' => 'textarea',
'#rows' => 3,
'#title' => $this->t("Words to be forced to capitalized by the 'capitalizator'"),
'#description' => $this->t("Enter a space separated list of words to be forced by the 'capitalizator'. This will be applied only to the words that are listed. This field is case sensitive. E.g.: if you want to capitalize your brand's name."),
'#default_value' => $capitalizator_forced_words,
'#states' => [
'visible' => [
':input[name="' . EasyBreadcrumbConstants::CAPITALIZATOR_MODE . '"]' => ['value' => 'ucforce'],
],
'invisible' => [
':input[name="' . EasyBreadcrumbConstants::CAPITALIZATOR_MODE . '"]' => ['!value' => 'ucforce'],
],
],
];
$details_advanced[EasyBreadcrumbConstants::CAPITALIZATOR_FORCED_WORDS_CASE_SENSITIVITY] = [
'#type' => 'checkbox',
'#title' => $this->t("Use case sensitivity when matching words to be forced to capitalization."),
'#default_value' => $config->get(EasyBreadcrumbConstants::CAPITALIZATOR_FORCED_WORDS_CASE_SENSITIVITY),
'#states' => [
'visible' => [
':input[name="' . EasyBreadcrumbConstants::CAPITALIZATOR_MODE . '"]' => ['value' => 'ucforce'],
],
'invisible' => [
':input[name="' . EasyBreadcrumbConstants::CAPITALIZATOR_MODE . '"]' => ['!value' => 'ucforce'],
],
],
'#description' => $this->t("If checked, it matches drupal with drupal, druPAL with druPAL. Unchecked, it matches drupal with Drupal, drupal with druPAL."),
];
$details_advanced[EasyBreadcrumbConstants::CAPITALIZATOR_FORCED_WORDS_FIRST_LETTER] = [
'#type' => 'checkbox',
'#title' => $this->t("Make the first letters of each segment capitalized."),
'#default_value' => $config->get(EasyBreadcrumbConstants::CAPITALIZATOR_FORCED_WORDS_FIRST_LETTER),
'#states' => [
'visible' => [
':input[name="' . EasyBreadcrumbConstants::CAPITALIZATOR_MODE . '"]' => ['value' => 'ucforce'],
],
'invisible' => [
':input[name="' . EasyBreadcrumbConstants::CAPITALIZATOR_MODE . '"]' => ['!value' => 'ucforce'],
],
],
];
$form = [];
// Inserts the details for grouping general settings fields.
$form[EasyBreadcrumbConstants::MODULE_NAME][] = $details_general;
$form[EasyBreadcrumbConstants::MODULE_NAME][] = $details_advanced;
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$settings = $this->configFactory->getEditable(EasyBreadcrumbConstants::MODULE_SETTINGS);
$values = $form_state->cleanValues()->getValues();
foreach ($values as $field_key => $field_value) {
$settings->set($field_key, $field_value);
}
$settings->save();
parent::submitForm($form, $form_state);
}
/**
* Pre-processes the list of words for storing them as an array.
*
* Replaces line-endings by spaces and splits words by spaces.
* E.g.: array('of','and').
*
* @param string $words
* A string of words.
*
* @return array
* An array of processed words.
*/
private function processValuesToArray($words) {
$words_arr = [];
$words = preg_replace('/\r*\n+/', ' ', $words);
$words = trim($words);
$words_arr_aux = $words === '' ? [] : preg_split('/\s+/', $words);
foreach ($words_arr_aux as $word) {
$words_arr[$word] = $word;
}
return $words_arr;
}
}
name: 'Easy Breadcrumb Testing'
description: 'Provides routes and controllers for automated testing.'
package: Testing
core: 8.x
core_version_requirement: ^8 || ^9
type: module
# Information added by Drupal.org packaging script on 2020-07-22
version: '8.x-1.13'
project: 'easy_breadcrumb'
datestamp: 1595377204
easy_breadcrumb_test.title_string:
path: /test/easy-breadcrumb
defaults:
_title: 'Easy Breadcrumb'
_controller: '\Drupal\easy_breadcrumb_test\Controller\TestRouteController::page'
easy_breadcrumb_test.title_formattable_markup:
path: /test/easy-breadcrumb-formattable
defaults:
_controller: '\Drupal\easy_breadcrumb_test\Controller\TestRouteController::page'
_title_callback: '\Drupal\easy_breadcrumb_test\Controller\TestRouteController::pageTitleFormattableMarkup'
easy_breadcrumb_test.title_markup:
path: /test/easy-breadcrumb-markup
defaults:
_controller: '\Drupal\easy_breadcrumb_test\Controller\TestRouteController::page'
_title_callback: '\Drupal\easy_breadcrumb_test\Controller\TestRouteController::pageTitleMarkup'
easy_breadcrumb_test.title_translatable_markup:
path: /test/easy-breadcrumb-translatable
defaults:
_controller: '\Drupal\easy_breadcrumb_test\Controller\TestRouteController::page'
_title_callback: '\Drupal\easy_breadcrumb_test\Controller\TestRouteController::pageTitleTranslatableMarkup'
easy_breadcrumb_test.title_render_array:
path: /test/easy-breadcrumb-render-array
defaults:
_controller: '\Drupal\easy_breadcrumb_test\Controller\TestRouteController::page'
_title_callback: '\Drupal\easy_breadcrumb_test\Controller\TestRouteController::pageTitleRender'
<?php
namespace Drupal\easy_breadcrumb_test\Controller;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Render\Markup;
/**
* Provides block routines for search server-specific routes.
*/
class TestRouteController extends ControllerBase {
/**
* Displays page for testing purposes.
*
* @return array
* An array suitable for drupal_render().
*/
public function page() {
return [
'#markup' => 'Test Page',
];
}
/**
* Returns the page title as FormattableMarkup.
*
* Among other places,
* used in Drupal\search_api\Controller\IndexController.php.
*
* @return \Drupal\Component\Render\FormattableMarkup
* The page title.
*/
public function pageTitleFormattableMarkup() {
return new FormattableMarkup('Type: @type', ['@type' => FormattableMarkup::class]);
}
/**
* Returns the page title as Markup.
*
* Used in views page titles.
*
* @return \Drupal\Component\Render\Markup
* The page title.
*/
public function pageTitleMarkup() {
return Markup::create(Xss::filter('Markup'));
}
/**
* Returns the page title as TranslatableMarkup.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* The page title.
*/
public function pageTitleTranslatableMarkup() {
return $this->t('TranslatableMarkup');
}
/**
* Returns the page title as FormattableMarkup.
*
* @return array
* The page title.
*/
public function pageTitleRender() {
return [
'#markup' => 'this is a string',
];
}
}
<?php
namespace Drupal\Tests\easy_breadcrumb\Kernel;
use Drupal\Core\Routing\RequestContext;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Url;
use Drupal\easy_breadcrumb\EasyBreadcrumbBuilder;
use Drupal\easy_breadcrumb\EasyBreadcrumbConstants;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* Tests the easy breadcrumb builder.
*
* @group easy_breadcrumb
*/
class EasyBreadcrumbBuilderTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['easy_breadcrumb', 'system', 'easy_breadcrumb_test'];
/**
* Tests the front page with an invalid path.
*/
public function testFrontpageWithInvalidPaths() {
\Drupal::configFactory()->getEditable(EasyBreadcrumbConstants::MODULE_SETTINGS)
->set('include_invalid_paths', TRUE)
->set('include_title_segment', TRUE)
->save();
\Drupal::configFactory()->getEditable('system.site')
->set('page.front', '/path')
->save();
$request_context = new RequestContext();
$breadcrumb_builder = new EasyBreadcrumbBuilder($request_context,
\Drupal::service('access_manager'),
\Drupal::service('router'),
\Drupal::service('request_stack'),
\Drupal::service('path_processor_manager'),
\Drupal::service('config.factory'),
\Drupal::service('title_resolver'),
\Drupal::service('current_user'),
\Drupal::service('path.current'),
\Drupal::service('plugin.manager.menu.link'),
\Drupal::service('language_manager'),
\Drupal::service('entity_type.manager'),
\Drupal::service('entity.repository'),
\Drupal::service('logger.factory'),
\Drupal::service('messenger'),
\Drupal::service('module_handler')
);
$route_match = new RouteMatch('test_front', new Route('/front'));
$result = $breadcrumb_builder->build($route_match);
$this->assertCount(0, $result->getLinks());
}
/**
* Provides data for the get title string test.
*/
public function providerTestGetTitleString() {
return [
['easy_breadcrumb_test.title_string'],
['easy_breadcrumb_test.title_formattable_markup'],
['easy_breadcrumb_test.title_markup'],
['easy_breadcrumb_test.title_translatable_markup'],
['easy_breadcrumb_test.title_render_array'],
];
}
/**
* Tests getting title string from the various ways route titles can be set.
*
* @param string $route_name
* The route to test.
*
* @dataProvider providerTestGetTitleString
*/
public function testGetTitleString($route_name) {
$url = Url::fromRoute($route_name);
$request_context = new RequestContext();
$breadcrumb_builder = new EasyBreadcrumbBuilder($request_context,
\Drupal::service('access_manager'),
\Drupal::service('router'),
\Drupal::service('request_stack'),
\Drupal::service('path_processor_manager'),
\Drupal::service('config.factory'),
\Drupal::service('title_resolver'),
\Drupal::service('current_user'),
\Drupal::service('path.current'),
\Drupal::service('plugin.manager.menu.link'),
\Drupal::service('language_manager'),
\Drupal::service('entity_type.manager'),
\Drupal::service('entity.repository'),
\Drupal::service('logger.factory'),
\Drupal::service('messenger'),
\Drupal::service('module_handler')
);
$request = Request::create($url->getInternalPath());
$router = \Drupal::service('router.no_access_checks');
$route_match = new RouteMatch($route_name, $router->match($url->getInternalPath())['_route_object']);
$result = $breadcrumb_builder->getTitleString($request, $route_match, []);
$this->assertIsString($result);
}
}
...@@ -13,6 +13,7 @@ regions: ...@@ -13,6 +13,7 @@ regions:
top_menu: 'Top Header menu' top_menu: 'Top Header menu'
primary_menu: 'Primary menu' primary_menu: 'Primary menu'
secondary_menu: 'Secondary menu' secondary_menu: 'Secondary menu'
banner: Banner
page_top: 'Page top' page_top: 'Page top'
page_bottom: 'Page bottom' page_bottom: 'Page bottom'
breadcrumb: Breadcrumb breadcrumb: Breadcrumb
......
...@@ -15,9 +15,8 @@ global-style: ...@@ -15,9 +15,8 @@ global-style:
css/videos.css: {} css/videos.css: {}
css/leadership.css: {} css/leadership.css: {}
css/product-main.css: {} css/product-main.css: {}
js: js:
theme:
js/jquery.min.js: {}
js/slick.min.js: {} js/slick.min.js: {}
js/custom.js: {} js/custom.js: {}
js/mwheelIntent.js: {} js/mwheelIntent.js: {}
...@@ -27,3 +26,5 @@ global-style: ...@@ -27,3 +26,5 @@ global-style:
js/jquery.mousewheel.js: {} js/jquery.mousewheel.js: {}
js/jquery.jscrollpane.min.js: {} js/jquery.jscrollpane.min.js: {}
js/imagesloaded.pkgd.min.js: {} js/imagesloaded.pkgd.min.js: {}
dependencies:
- core/jquery
...@@ -1888,6 +1888,82 @@ ...@@ -1888,6 +1888,82 @@
$("html, body").animate({ scrollTop: a }, 700); $("html, body").animate({ scrollTop: a }, 700);
}); });
console.log("header");
// search button function start
$('.h_search_dta').on('click', function(){
$(this).parents('body').addClass('h_scrollNone').find('.h_site_search').show();
});
$(document).click(function (e) {
if (!$(e.target).is('.hh_site_search_box *, .hh_site_search_box, .h_search_dta, .h_search_dta *')) {
$('body').removeClass('h_scrollNone').find('.h_site_search').hide();
}
});
// search button function end
// change region button function start
$('.h_cr_b a').on('click', function(e){
e.preventDefault();
$(this).parents('body').addClass('h_scrollNone').find('.h_header_langBox').show();
});
$(document).click(function (e) {
if (!$(e.target).is('.h_cr_b *, .h_cr_b, .h_header_langBox_box, .h_header_langBox_box *')) {
$('body').removeClass('h_scrollNone').find('.h_header_langBox').hide();
}
});
// change region button function end
$('.h_menu_btn').on('click', function(e){
var a = $(this);
if (a.hasClass("open")) {
a.parents('body').removeClass('menu_body_scrollNone');
$(this).removeClass('open').parents('.h_rc_b').removeClass('activeMenucon');
} else {
a.parents('body').addClass('menu_body_scrollNone');
$(this).addClass('open').parents('.h_rc_b').addClass('activeMenucon');
}
});
$('.h_nav_main li em').on('click', function(e){
$(this).parents('li').toggleClass('activeSM').siblings().removeClass('activeSM');
});
// menu container code start
//activeSM
/* const $menuBtn = document.querySelector(".menu-btn");
let isMenuOpen = false;
$menuBtn.addEventListener("click", () => {
if (!isMenuOpen) {
$menuBtn.classList.add("open");
} else {
$menuBtn.classList.remove("open");
}
isMenuOpen = !isMenuOpen;
}); */
$('.f_l_cb .h6_h_c').on('click', function(){
var a = $(this).parent().find("ul");
$(this).parents('.f_cb_m').siblings('.f_cb_m').find('.f_l_cb').removeClass('activeMenu').find('ul').slideUp();
$(this).parent().siblings('.f_l_cb').removeClass('activeMenu').find('ul').slideUp();
if (a.is(":visible")) {
$(this).parent().find('ul').slideUp();
$(this).parent().removeClass('activeMenu');
} else {
$(this).parent().find('ul').slideDown();
$(this).parent().addClass('activeMenu');
}
});
})(jQuery); })(jQuery);
......
...@@ -53,6 +53,7 @@ ...@@ -53,6 +53,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="layout-container"> <div class="layout-container">
<div class="layout-content"> <div class="layout-content">
...@@ -60,10 +61,7 @@ ...@@ -60,10 +61,7 @@
</div>{# /.layout-content #} </div>{# /.layout-content #}
</div> </div>
{% if page.footer %}
{% if page.footer %}
<div class="container-fluid f_l_c_b"> <div class="container-fluid f_l_c_b">
<div class="row"> <div class="row">
<div class="container"> <div class="container">
...@@ -81,6 +79,12 @@ ...@@ -81,6 +79,12 @@
<div class="col-lg-3 col-12 f_cb_m"> <div class="col-lg-3 col-12 f_cb_m">
{{ page.footer_second }} {{ page.footer_second }}
</div> </div>
<div class="col-lg-3 col-12 f_cb_m">
{{ page.footer_third }}
</div>
<div class="col-lg-3 col-12 f_cb_m">
{{ page.footer_fourth }}
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -88,7 +92,7 @@ ...@@ -88,7 +92,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
...@@ -115,22 +119,4 @@ ...@@ -115,22 +119,4 @@
</div> </div>
<!-- footer copyright strip end --> <!-- footer copyright strip end -->
<script>
(function($) {
console.log("footer");
$('.f_l_cb .h6_h_c').on('click', function(){
var a = $(this).parent().find("ul");
$(this).parents('.f_cb_m').siblings('.f_cb_m').find('.f_l_cb').removeClass('activeMenu').find('ul').slideUp();
$(this).parent().siblings('.f_l_cb').removeClass('activeMenu').find('ul').slideUp();
if (a.is(":visible")) {
$(this).parent().find('ul').slideUp();
$(this).parent().removeClass('activeMenu');
} else {
$(this).parent().find('ul').slideDown();
$(this).parent().addClass('activeMenu');
}
});
})(jQuery);
</script>
...@@ -53,17 +53,45 @@ ...@@ -53,17 +53,45 @@
</div> </div>
</div> </div>
</div> </div>
<div class="layout-container"> <!-- banner section start -->
<div class="b_o_c_b">
<div class="b_main_slider static_banner">
<div class="b_slide_item">
<img src="{{ base_path ~ directory }}/images/static_banner.jpg" class="b_desktop_view" alt="Color &amp; Comfort" />
<img src="{{ base_path ~ directory }}/images/static_mobile_banner.jpg" class="b_mobile_view" alt="Color &amp; Comfort" />
<div class="s_b_o_c">
<div class="container">
<div class="s_c_iw">
<div class="sb_ibcd">
<h1 class="s_p_h_c">{{ label }}</h1>
<p>DIC India is a part of the World's largest manufacturer of printing Inks and allied material, DIC Corporation of Japan. DIC Japan</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- banner section end -->
<div class="container-fluid">
<div class="row">
<div class="container">
<div class="row">
<div class="col-12">
{{ page.breadcrumb }}
</div>
</div>
</div>
</div>
</div>
<div class="layout-container">
<div class="layout-content"> <div class="layout-content">
{{ page.content }} {{ page.content }}
</div>{# /.layout-content #} </div>{# /.layout-content #}
</div> </div>
{% if page.footer %}
{% if page.footer %}
<div class="container-fluid f_l_c_b"> <div class="container-fluid f_l_c_b">
<div class="row"> <div class="row">
<div class="container"> <div class="container">
...@@ -81,6 +109,12 @@ ...@@ -81,6 +109,12 @@
<div class="col-lg-3 col-12 f_cb_m"> <div class="col-lg-3 col-12 f_cb_m">
{{ page.footer_second }} {{ page.footer_second }}
</div> </div>
<div class="col-lg-3 col-12 f_cb_m">
{{ page.footer_third }}
</div>
<div class="col-lg-3 col-12 f_cb_m">
{{ page.footer_fourth }}
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -88,7 +122,7 @@ ...@@ -88,7 +122,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
...@@ -115,22 +149,4 @@ ...@@ -115,22 +149,4 @@
</div> </div>
<!-- footer copyright strip end --> <!-- footer copyright strip end -->
<script>
(function($) {
console.log("footer");
$('.f_l_cb .h6_h_c').on('click', function(){
var a = $(this).parent().find("ul");
$(this).parents('.f_cb_m').siblings('.f_cb_m').find('.f_l_cb').removeClass('activeMenu').find('ul').slideUp();
$(this).parent().siblings('.f_l_cb').removeClass('activeMenu').find('ul').slideUp();
if (a.is(":visible")) {
$(this).parent().find('ul').slideUp();
$(this).parent().removeClass('activeMenu');
} else {
$(this).parent().find('ul').slideDown();
$(this).parent().addClass('activeMenu');
}
});
})(jQuery);
</script>
{#
/**
* @file
* Theme override for a breadcrumb trail.
*
* Available variables:
* - breadcrumb: Breadcrumb trail items.
*/
#}
<!-- breadcurmb start -->
{% if breadcrumb %}
<nav class="border-bottom">
<ol class="cd-breadcrumb custom-separator">
{% for item in breadcrumb %}
{% if item.url %}
<li>
<a href="{{ item.url }}">{{ item.text }}</a> <i class="fa fa-angle-right" aria-hidden="true"></i>
</li>
{% else %}
<li class="breadcrumb_select">{{ item.text }}</li>
{% endif %}
{% endfor %}
</ol>
</nav>
{% endif %}
<!-- breadcurmd end -->
...@@ -33,8 +33,9 @@ ...@@ -33,8 +33,9 @@
<div class="h_menu_c_b"> <div class="h_menu_c_b">
<ul class="h_nav_main"> <ul class="h_nav_main">
{% else %} {% else %}
<div class="h_menu_c_b"> <div class="header-nav-item-child-list">
<ul class="h_nav_main"> <em><i class="fa fa-angle-down" aria-hidden="true"></i></em>
<ul class="pure-g">
{% endif %} {% endif %}
{% for item in items %} {% for item in items %}
{% {%
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment