diff --git a/.travis-before-script.sh b/.travis-before-script.sh
new file mode 100644
index 0000000..21dc82f
--- /dev/null
+++ b/.travis-before-script.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+set -e $DRUPAL_TI_DEBUG
+
+# Ensure the right Drupal version is installed.
+# Note: This function is re-entrant.
+drupal_ti_ensure_drupal
+
+# Add needed dependencies.
+cd "$DRUPAL_TI_DRUPAL_DIR"
+
+# These variables come from environments/drupal-*.sh
+mkdir -p "$DRUPAL_TI_MODULES_PATH"
+cd "$DRUPAL_TI_MODULES_PATH"
+
+# Download token 8.x-1.x
+git clone --depth 1 --branch 8.x-1.x http://git.drupal.org/project/token.git
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..112aea1
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,115 @@
+# @file
+# .travis.yml - Drupal for Travis CI Integration
+#
+# Template provided by https://github.com/LionsAd/drupal_ti.
+#
+# Based for simpletest upon:
+# https://github.com/sonnym/travis-ci-drupal-module-example
+
+language: php
+
+sudo: false
+
+php:
+ - 5.5
+ - 5.6
+ - 7
+ - hhvm
+
+matrix:
+ fast_finish: true
+ allow_failures:
+ - php: 7
+ - php: hhvm
+
+env:
+ global:
+ # add composer's global bin directory to the path
+ # see: https://github.com/drush-ops/drush#install---composer
+ - PATH="$PATH:$HOME/.composer/vendor/bin"
+
+ # Configuration variables.
+ - DRUPAL_TI_MODULE_NAME="pathauto"
+ - DRUPAL_TI_SIMPLETEST_GROUP="pathauto"
+
+ # Define runners and environment vars to include before and after the
+ # main runners / environment vars.
+ #- DRUPAL_TI_SCRIPT_DIR_BEFORE="./.drupal_ti/before"
+ #- DRUPAL_TI_SCRIPT_DIR_AFTER="./drupal_ti/after"
+
+ # The environment to use, supported are: drupal-7, drupal-8
+ - DRUPAL_TI_ENVIRONMENT="drupal-8"
+
+ # Drupal specific variables.
+ - DRUPAL_TI_DB="drupal_travis_db"
+ - DRUPAL_TI_DB_URL="mysql://root:@127.0.0.1/drupal_travis_db"
+ # Note: Do not add a trailing slash here.
+ - DRUPAL_TI_WEBSERVER_URL="http://127.0.0.1"
+ - DRUPAL_TI_WEBSERVER_PORT="8080"
+
+ # Simpletest specific commandline arguments, the DRUPAL_TI_SIMPLETEST_GROUP is appended at the end.
+ - DRUPAL_TI_SIMPLETEST_ARGS="--verbose --color --concurrency 4 --url $DRUPAL_TI_WEBSERVER_URL:$DRUPAL_TI_WEBSERVER_PORT"
+
+ # === Behat specific variables.
+ # This is relative to $TRAVIS_BUILD_DIR
+ - DRUPAL_TI_BEHAT_DIR="./tests/behat"
+ # These arguments are passed to the bin/behat command.
+ - DRUPAL_TI_BEHAT_ARGS=""
+ # Specify the filename of the behat.yml with the $DRUPAL_TI_DRUPAL_DIR variables.
+ - DRUPAL_TI_BEHAT_YML="behat.yml.dist"
+ # This is used to setup Xvfb.
+ - DRUPAL_TI_BEHAT_SCREENSIZE_COLOR="1280x1024x16"
+ # The version of seleniumthat should be used.
+ - DRUPAL_TI_BEHAT_SELENIUM_VERSION="2.44"
+ # Set DRUPAL_TI_BEHAT_DRIVER to "selenium" to use "firefox" or "chrome" here.
+ - DRUPAL_TI_BEHAT_DRIVER="phantomjs"
+ - DRUPAL_TI_BEHAT_BROWSER="firefox"
+
+ # PHPUnit specific commandline arguments.
+ - DRUPAL_TI_PHPUNIT_ARGS="--verbose --debug"
+ # Specifying the phpunit-core src/ directory is useful when e.g. a vendor/
+ # directory is present in the module directory, which phpunit would then
+ # try to find tests in. This option is relative to $TRAVIS_BUILD_DIR.
+ #- DRUPAL_TI_PHPUNIT_CORE_SRC_DIRECTORY="./tests/src"
+
+ # Code coverage via coveralls.io
+ - DRUPAL_TI_COVERAGE="satooshi/php-coveralls:0.6.*"
+ # This needs to match your .coveralls.yml file.
+ - DRUPAL_TI_COVERAGE_FILE="build/logs/clover.xml"
+
+ # Debug options
+ #- DRUPAL_TI_DEBUG="-x -v"
+ # Set to "all" to output all files, set to e.g. "xvfb selenium" or "selenium",
+ # etc. to only output those channels.
+ #- DRUPAL_TI_DEBUG_FILE_OUTPUT="selenium xvfb webserver"
+
+ matrix:
+ # [[[ SELECT ANY OR MORE OPTIONS ]]]
+ #- DRUPAL_TI_RUNNERS="phpunit"
+ #- DRUPAL_TI_RUNNERS="simpletest"
+ #- DRUPAL_TI_RUNNERS="behat"
+ - DRUPAL_TI_RUNNERS="phpunit-core simpletest"
+
+mysql:
+ database: drupal_travis_db
+ username: root
+ encoding: utf8
+
+before_install:
+ - composer self-update
+ - composer global require "lionsad/drupal_ti:1.*"
+ - drupal-ti before_install
+
+install:
+ - drupal-ti install
+
+before_script:
+ # Install token 8.x-1.x
+ - drupal-ti --include .travis-before-script.sh
+ - drupal-ti before_script
+
+script:
+ - drupal-ti script
+
+after_script:
+ - drupal-ti after_script
diff --git a/INSTALL.txt b/INSTALL.txt
deleted file mode 100644
index 586fdfe..0000000
--- a/INSTALL.txt
+++ /dev/null
@@ -1,48 +0,0 @@
-**Installation:
-
-Pathauto is an extension to the path module, which must be enabled.
-
-Pathauto also relies on the Token module, which must be downloaded and
-enabled separately.
-
-1. Unpack the Pathauto folder and contents in the appropriate modules
-directory of your Drupal installation. This is probably
- sites/all/modules/
-2. Enable the Pathauto module in the administration tools.
-3. If you're not using Drupal's default administrative account, make
-sure "administer pathauto" is enabled through access control administration.
-4. Visit the Pathauto settings page and make appropriate configurations
- For 5.x: Administer > Site configuration > Pathauto
- For 6.x: Administer > Site building > URL alias > Automated alias settings
-
-**Transliteration support:
-If you desire transliteration support in the creation of URLs (e.g. the
-replacement of Á with A) then you will need to install the Transliteration
-module, which can be found at http://drupal.org/project/transliteration
-
-Once you've installed and enabled the module, simply go to
-admin/config/search/path/settings and check the "Transliterate prior to
-creating alias" box and path aliases should now be transliterated automagically.
-
-**Upgrading from previous versions:
-If you are upgrading from Pathauto 5.x-1.x to 5.x-2.x (or 6.x-2.x) then you
-will probably need to change your patterns.
-
-For content patterns:
- [user] is now [author-name]
- [cat] is now [term]
-
-There may be other changes as well. Please review the pattern examples on
- Administration > Site Configuration > Pathauto
-
-If you upgraded from Pathauto 5.x-1.x directly without enabling Token
-first then you will need to
- 1) download/install the Token module
- 2) disable the Pathauto module
- 3) re-enable the Pathauto module
-
-Upgrade to 6.x:
-Note that the settings page has moved so that it is more logically grouped with
-other URL alias related items under
- Administer > Site building > URL alias > Automated alias settings
-
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a65d0f1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,90 @@
+#Pathauto [![Build Status](https://travis-ci.org/md-systems/pathauto.svg?branch=8.x-1.x)](https://travis-ci.org/md-systems/pathauto)
+
+Please read this file and also the INSTALL.txt.
+They contain answers to many common questions.
+If you are developing for this module, the API.txt may be interesting.
+If you are upgrading, check the CHANGELOG.txt for major changes.
+
+##Description
+
+The Pathauto module provides support functions for other modules to
+automatically generate aliases based on appropriate criteria, with a
+central settings path for site administrators.
+
+Implementations are provided for core entity types: content, taxonomy terms,
+and users (including blogs and forum pages).
+
+Pathauto also provides a way to delete large numbers of aliases. This feature
+is available at Administer > Configuration > Search and metadata > URL aliases
+> Delete aliases.
+
+##Benefits
+
+Besides making the page address more reflective of its content than
+"node/138", it's important to know that modern search engines give
+heavy weight to search terms which appear in a page's URL. By
+automatically using keywords based directly on the page content in the URL,
+relevant search engine hits for your page can be significantly
+enhanced.
+
+##Installation AND Upgrades
+
+See the INSTALL.txt file.
+
+##Notices
+
+Pathauto just adds URL aliases to content, users, and taxonomy terms.
+Because it's an alias, the standard Drupal URL (for example node/123 or
+taxonomy/term/1) will still function as normal. If you have external links
+to your site pointing to standard Drupal URLs, or hardcoded links in a module,
+template, content or menu which point to standard Drupal URLs it will bypass
+the alias set by Pathauto.
+
+There are reasons you might not want two URLs for the same content on your
+site. If this applies to you, please note that you will need to update any
+hard coded links in your content or blocks.
+
+If you use the "system path" (i.e. node/10) for menu items and settings like
+that, Drupal will replace it with the url_alias.
+
+For external links, you might want to consider the Path Redirect or
+Global Redirect modules, which allow you to set forwarding either per item or
+across the site to your aliased URLs.
+
+URLs (not) Getting Replaced With Aliases:
+Please bear in mind that only URLs passed through Drupal's l() or url()
+functions will be replaced with their aliases during page output. If a module
+or your template contains hardcoded links, such as 'href="node/$node->nid"'
+those won't get replaced with their corresponding aliases. Use the
+Drupal API instead:
+
+* 'href="'. url("node/$node->nid") .'"' or
+* l("Your link title", "node/$node->nid")
+
+See http://api.drupal.org/api/HEAD/function/url and
+http://api.drupal.org/api/HEAD/function/l for more information.
+
+## Disabling Pathauto for a specific content type (or taxonomy)
+
+When the pattern for a content type is left blank, the default pattern will be
+used. But if the default pattern is also blank, Pathauto will be disabled
+for that content type.
+
+##Credits:
+
+The original module combined the functionality of Mike Ryan's autopath with
+Tommy Sundstrom's path_automatic.
+
+Significant enhancements were contributed by jdmquin @ www.bcdems.net.
+
+Matt England added the tracker support (tracker support has been removed in
+recent changes).
+
+Other suggestions and patches contributed by the Drupal community.
+
+Current maintainers:
+
+- Dave Reid - http://www.davereid.net
+- Greg Knaddison - http://www.knaddison.com
+- Mike Ryan - http://mikeryan.name
+- Frederik 'Freso' S. Olesen - http://freso.dk
diff --git a/README.txt b/README.txt
deleted file mode 100644
index fc56959..0000000
--- a/README.txt
+++ /dev/null
@@ -1,81 +0,0 @@
-Please read this file and also the INSTALL.txt.
-They contain answers to many common questions.
-If you are developing for this module, the API.txt may be interesting.
-If you are upgrading, check the CHANGELOG.txt for major changes.
-
-** Description:
-The Pathauto module provides support functions for other modules to
-automatically generate aliases based on appropriate criteria, with a
-central settings path for site administrators.
-
-Implementations are provided for core entity types: content, taxonomy terms,
-and users (including blogs and forum pages).
-
-Pathauto also provides a way to delete large numbers of aliases. This feature
-is available at Administer > Configuration > Search and metadata > URL aliases
-> Delete aliases.
-
-** Benefits:
-Besides making the page address more reflective of its content than
-"node/138", it's important to know that modern search engines give
-heavy weight to search terms which appear in a page's URL. By
-automatically using keywords based directly on the page content in the URL,
-relevant search engine hits for your page can be significantly
-enhanced.
-
-** Installation AND Upgrades:
-See the INSTALL.txt file.
-
-** Notices:
-Pathauto just adds URL aliases to content, users, and taxonomy terms.
-Because it's an alias, the standard Drupal URL (for example node/123 or
-taxonomy/term/1) will still function as normal. If you have external links
-to your site pointing to standard Drupal URLs, or hardcoded links in a module,
-template, content or menu which point to standard Drupal URLs it will bypass
-the alias set by Pathauto.
-
-There are reasons you might not want two URLs for the same content on your
-site. If this applies to you, please note that you will need to update any
-hard coded links in your content or blocks.
-
-If you use the "system path" (i.e. node/10) for menu items and settings like
-that, Drupal will replace it with the url_alias.
-
-For external links, you might want to consider the Path Redirect or
-Global Redirect modules, which allow you to set forwarding either per item or
-across the site to your aliased URLs.
-
-URLs (not) Getting Replaced With Aliases:
-Please bear in mind that only URLs passed through Drupal's l() or url()
-functions will be replaced with their aliases during page output. If a module
-or your template contains hardcoded links, such as 'href="node/$node->nid"'
-those won't get replaced with their corresponding aliases. Use the
-Drupal API instead:
-
-* 'href="'. url("node/$node->nid") .'"' or
-* l("Your link title", "node/$node->nid")
-
-See http://api.drupal.org/api/HEAD/function/url and
-http://api.drupal.org/api/HEAD/function/l for more information.
-
-** Disabling Pathauto for a specific content type (or taxonomy)
-When the pattern for a content type is left blank, the default pattern will be
-used. But if the default pattern is also blank, Pathauto will be disabled
-for that content type.
-
-** Credits:
-The original module combined the functionality of Mike Ryan's autopath with
-Tommy Sundstrom's path_automatic.
-
-Significant enhancements were contributed by jdmquin @ www.bcdems.net.
-
-Matt England added the tracker support (tracker support has been removed in
-recent changes).
-
-Other suggestions and patches contributed by the Drupal community.
-
-Current maintainers:
- Dave Reid - http://www.davereid.net
- Greg Knaddison - http://www.knaddison.com
- Mike Ryan - http://mikeryan.name
- Frederik 'Freso' S. Olesen - http://freso.dk
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..0b1952a
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,6 @@
+{
+ "name": "drupal/pathauto",
+ "description": "Provides a generic set of views plugins a mechanism for modules to automatically generate aliases for the content they manage.",
+ "type": "drupal-module",
+ "license": "GPL-2.0+"
+}
diff --git a/config/install/pathauto.pattern.yml b/config/install/pathauto.pattern.yml
new file mode 100644
index 0000000..cde1de5
--- /dev/null
+++ b/config/install/pathauto.pattern.yml
@@ -0,0 +1,12 @@
+patterns:
+ node:
+ default: '/content/[node:title]'
+
+ taxonomy_term:
+ default: '/[term:vocabulary]/[term:name]'
+
+ forum:
+ default: '/[term:vocabulary]/[term:name]'
+
+ user:
+ default: '/users/[user:name]'
diff --git a/config/install/pathauto.settings.yml b/config/install/pathauto.settings.yml
new file mode 100644
index 0000000..2a331f2
--- /dev/null
+++ b/config/install/pathauto.settings.yml
@@ -0,0 +1,12 @@
+punctuation:
+ hyphen: 1
+verbose : FALSE
+separator : '-'
+max_length : 100
+max_component_length: 100
+transliterate : FALSE
+reduce_ascii : FALSE
+ignore_words : ', in, is,that, the , this, with, '
+case : TRUE
+ignore_words : 'a, an, as, at, before, but, by, for, from, is, in, into, like, of, off, on, onto, per, since, than, the, this, that, to, up, via, with'
+update_action : 2
diff --git a/config/optional/system.action.pathauto_update_alias_node.yml b/config/optional/system.action.pathauto_update_alias_node.yml
new file mode 100644
index 0000000..5abe03b
--- /dev/null
+++ b/config/optional/system.action.pathauto_update_alias_node.yml
@@ -0,0 +1,9 @@
+id: pathauto_update_alias_node
+label: 'Update URL-Alias'
+status: true
+langcode: en
+type: node
+plugin: pathauto_update_alias
+dependencies:
+ module:
+ - node
diff --git a/config/optional/system.action.pathauto_update_alias_user.yml b/config/optional/system.action.pathauto_update_alias_user.yml
new file mode 100644
index 0000000..58b1b70
--- /dev/null
+++ b/config/optional/system.action.pathauto_update_alias_user.yml
@@ -0,0 +1,9 @@
+id: pathauto_update_alias_user
+label: 'Update URL-Alias'
+status: true
+langcode: en
+type: user
+plugin: pathauto_update_alias
+dependencies:
+ module:
+ - user
diff --git a/config/schema/pathauto.schema.yml b/config/schema/pathauto.schema.yml
new file mode 100644
index 0000000..917e674
--- /dev/null
+++ b/config/schema/pathauto.schema.yml
@@ -0,0 +1,54 @@
+pathauto.pattern:
+ type: config_object
+ mapping:
+ patterns:
+ type: sequence
+ sequence:
+ type: mapping
+ mapping:
+ default:
+ type: string
+ bundles:
+ type: sequence
+ sequence:
+ type: mapping
+ mapping:
+ default:
+ type: string
+ languages:
+ type: sequence
+ sequence:
+ type: string
+
+
+pathauto.settings:
+ type: config_object
+ mapping:
+ punctuation:
+ type: sequence
+ sequence:
+ type: integer
+ verbose:
+ type: boolean
+ separator:
+ type: string
+ max_length:
+ type: integer
+ max_component_length:
+ type: integer
+ transliterate:
+ type: boolean
+ reduce_ascii:
+ type: boolean
+ ignore_words:
+ type: string
+ case:
+ type: boolean
+ ignore_words:
+ type: string
+ update_action:
+ type: integer
+
+action.configuration.pathauto_update_alias:
+ type: action_configuration_default
+ label: 'Update URL-Alias'
diff --git a/pathauto.admin.inc b/pathauto.admin.inc
deleted file mode 100644
index b210386..0000000
--- a/pathauto.admin.inc
+++ /dev/null
@@ -1,431 +0,0 @@
-module;
- $patterndescr = $settings->patterndescr;
- $patterndefault = $settings->patterndefault;
- $groupheader = $settings->groupheader;
-
- $form[$module] = array(
- '#type' => 'fieldset',
- '#title' => $groupheader,
- '#collapsible' => TRUE,
- '#collapsed' => FALSE,
- );
-
- // Prompt for the default pattern for this module
- $variable = 'pathauto_' . $module . '_pattern';
- $form[$module][$variable] = array(
- '#type' => 'textfield',
- '#title' => $patterndescr,
- '#default_value' => variable_get($variable, $patterndefault),
- '#size' => 65,
- '#maxlength' => 1280,
- '#element_validate' => array('token_element_validate'),
- '#after_build' => array('token_element_validate'),
- '#token_types' => array($settings->token_type),
- '#min_tokens' => 1,
- '#parents' => array($variable),
- );
-
- // If the module supports a set of specialized patterns, set
- // them up here
- if (isset($settings->patternitems)) {
- foreach ($settings->patternitems as $itemname => $itemlabel) {
- $variable = 'pathauto_' . $module . '_' . $itemname . '_pattern';
- $form[$module][$variable] = array(
- '#type' => 'textfield',
- '#title' => $itemlabel,
- '#default_value' => variable_get($variable, ''),
- '#size' => 65,
- '#maxlength' => 1280,
- '#element_validate' => array('token_element_validate'),
- '#after_build' => array('token_element_validate'),
- '#token_types' => array($settings->token_type),
- '#min_tokens' => 1,
- '#parents' => array($variable),
- );
- }
- }
-
- // Show the token help relevant to this pattern type.
- $form[$module]['token_help'] = array(
- '#theme' => 'token_tree',
- '#token_types' => array($settings->token_type),
- '#dialog' => TRUE,
- );
- }
-
- return system_settings_form($form);
-}
-
-/**
- * Form builder; Configure the Pathauto settings.
- *
- * @ingroup forms
- * @see system_settings_form()
- */
-function pathauto_settings_form($form) {
- module_load_include('inc', 'pathauto');
-
- $form['pathauto_verbose'] = array(
- '#type' => 'checkbox',
- '#title' => t('Verbose'),
- '#default_value' => variable_get('pathauto_verbose', FALSE),
- '#description' => t('Display alias changes (except during bulk updates).'),
- );
-
- $form['pathauto_separator'] = array(
- '#type' => 'textfield',
- '#title' => t('Separator'),
- '#size' => 1,
- '#maxlength' => 1,
- '#default_value' => variable_get('pathauto_separator', '-'),
- '#description' => t('Character used to separate words in titles. This will replace any spaces and punctuation characters. Using a space or + character can cause unexpected results.'),
- );
-
- $form['pathauto_case'] = array(
- '#type' => 'radios',
- '#title' => t('Character case'),
- '#default_value' => variable_get('pathauto_case', PATHAUTO_CASE_LOWER),
- '#options' => array(
- PATHAUTO_CASE_LEAVE_ASIS => t('Leave case the same as source token values.'),
- PATHAUTO_CASE_LOWER => t('Change to lower case'),
- ),
- );
-
- $max_length = _pathauto_get_schema_alias_maxlength();
-
- $form['pathauto_max_length'] = array(
- '#type' => 'textfield',
- '#title' => t('Maximum alias length'),
- '#size' => 3,
- '#maxlength' => 3,
- '#default_value' => variable_get('pathauto_max_length', 100),
- '#min_value' => 1,
- '#max_value' => $max_length,
- '#description' => t('Maximum length of aliases to generate. 100 is the recommended length. @max is the maximum possible length. See Pathauto help for details.', array('@pathauto-help' => url('admin/help/pathauto'), '@max' => $max_length)),
- '#element_validate' => array('_pathauto_validate_numeric_element'),
- );
- $form['pathauto_max_component_length'] = array(
- '#type' => 'textfield',
- '#title' => t('Maximum component length'),
- '#size' => 3,
- '#maxlength' => 3,
- '#default_value' => variable_get('pathauto_max_component_length', 100),
- '#min_value' => 1,
- '#max_value' => $max_length,
- '#description' => t('Maximum text length of any component in the alias (e.g., [title]). 100 is the recommended length. @max is the maximum possible length. See Pathauto help for details.', array('@pathauto-help' => url('admin/help/pathauto'), '@max' => $max_length)),
- '#element_validate' => array('_pathauto_validate_numeric_element'),
- );
-
-
- $description = t('What should Pathauto do when updating an existing content item which already has an alias?');
- if (module_exists('redirect')) {
- $description .= ' ' . t('The Redirect module settings affect whether a redirect is created when an alias is deleted.', array('!url' => url('admin/config/search/redirect/settings')));
- }
- else {
- $description .= ' ' . t('Considering installing the Redirect module to get redirects when your aliases change.', array('!url' => 'http://drupal.org/project/redirect'));
- }
- $form['pathauto_update_action'] = array(
- '#type' => 'radios',
- '#title' => t('Update action'),
- '#default_value' => variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE),
- '#options' => array(
- PATHAUTO_UPDATE_ACTION_NO_NEW => t('Do nothing. Leave the old alias intact.'),
- PATHAUTO_UPDATE_ACTION_LEAVE => t('Create a new alias. Leave the existing alias functioning.'),
- PATHAUTO_UPDATE_ACTION_DELETE => t('Create a new alias. Delete the old alias.'),
- ),
- '#description' => $description,
- );
-
- $form['pathauto_transliterate'] = array(
- '#type' => 'checkbox',
- '#title' => t('Transliterate prior to creating alias'),
- '#default_value' => variable_get('pathauto_transliterate', FALSE) && module_exists('transliteration'),
- '#description' => t('When a pattern includes certain characters (such as those with accents) should Pathauto attempt to transliterate them into the US-ASCII alphabet? Transliteration is handled by the Transliteration module.'),
- '#access' => module_exists('transliteration'),
- );
-
- $form['pathauto_reduce_ascii'] = array(
- '#type' => 'checkbox',
- '#title' => t('Reduce strings to letters and numbers'),
- '#default_value' => variable_get('pathauto_reduce_ascii', FALSE),
- '#description' => t('Filters the new alias to only letters and numbers found in the ASCII-96 set.'),
- );
-
- $form['pathauto_ignore_words'] = array(
- '#type' => 'textarea',
- '#title' => t('Strings to Remove'),
- '#default_value' => variable_get('pathauto_ignore_words', PATHAUTO_IGNORE_WORDS),
- '#description' => t('Words to strip out of the URL alias, separated by commas. Do not use this to remove punctuation.'),
- '#wysiwyg' => FALSE,
- );
-
- $form['punctuation'] = array(
- '#type' => 'fieldset',
- '#title' => t('Punctuation'),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- );
-
- $punctuation = pathauto_punctuation_chars();
- foreach ($punctuation as $name => $details) {
- $details['default'] = PATHAUTO_PUNCTUATION_REMOVE;
- if ($details['value'] == variable_get('pathauto_separator', '-')) {
- $details['default'] = PATHAUTO_PUNCTUATION_REPLACE;
- }
- $form['punctuation']['pathauto_punctuation_' . $name] = array(
- '#type' => 'select',
- '#title' => $details['name'] . ' (' . check_plain($details['value']) . '
)',
- '#default_value' => variable_get('pathauto_punctuation_' . $name, $details['default']),
- '#options' => array(
- PATHAUTO_PUNCTUATION_REMOVE => t('Remove'),
- PATHAUTO_PUNCTUATION_REPLACE => t('Replace by separator'),
- PATHAUTO_PUNCTUATION_DO_NOTHING => t('No action (do not replace)'),
- ),
- );
- }
-
- return system_settings_form($form);
-}
-
-/**
- * Validate a form element that should have an numeric value.
- */
-function _pathauto_validate_numeric_element($element, &$form_state) {
- $value = $element['#value'];
-
- if (!is_numeric($value)) {
- form_error($element, t('The field %name is not a valid number.', array('%name' => $element['#title'])));
- }
- elseif (isset($element['#max_value']) && $value > $element['#max_value']) {
- form_error($element, t('The field %name cannot be greater than @max.', array('%name' => $element['#title'], '@max' => $element['#max_value'])));
- }
- elseif (isset($element['#min_value']) && $value < $element['#min_value']) {
- form_error($element, t('The field %name cannot be less than @min.', array('%name' => $element['#title'], '@min' => $element['#min_value'])));
- }
-}
-
-/**
- * Validate pathauto_settings_form form submissions.
- */
-function pathauto_settings_form_validate($form, &$form_state) {
- module_load_include('inc', 'pathauto');
-
- // Perform a basic check for HTML characters in the strings to remove field.
- if (strip_tags($form_state['values']['pathauto_ignore_words']) != $form_state['values']['pathauto_ignore_words']) {
- form_set_error('pathauto_ignore_words', t('The Strings to remove field must not contain HTML. Make sure to disable any WYSIWYG editors for this field.'));
- }
-
- // Validate that the separator is not set to be removed per http://drupal.org/node/184119
- // This isn't really all that bad so warn, but still allow them to save the value.
- $separator = $form_state['values']['pathauto_separator'];
- $punctuation = pathauto_punctuation_chars();
- foreach ($punctuation as $name => $details) {
- if ($details['value'] == $separator) {
- $action = $form_state['values']['pathauto_punctuation_' . $name];
- if ($action == PATHAUTO_PUNCTUATION_REMOVE) {
- drupal_set_message(t('You have configured the @name to be the separator and to be removed when encountered in strings. This can cause problems with your patterns and especially with the term:path token. You should probably set the action for @name to be "replace by separator".', array('@name' => $details['name'])), 'error');
- }
- }
- }
-}
-
-/**
- * Form contructor for path alias bulk update form.
- *
- * @see pathauto_bulk_update_form_submit()
- * @ingroup forms
- */
-function pathauto_bulk_update_form() {
- $form['#update_callbacks'] = array();
-
- $form['update'] = array(
- '#type' => 'checkboxes',
- '#title' => t('Select the types of un-aliased paths for which to generate URL aliases'),
- '#options' => array(),
- '#default_value' => array(),
- );
-
- $pathauto_settings = module_invoke_all('pathauto', 'settings');
- foreach ($pathauto_settings as $settings) {
- if (!empty($settings->batch_update_callback)) {
- $form['#update_callbacks'][$settings->batch_update_callback] = $settings;
- $form['update']['#options'][$settings->batch_update_callback] = $settings->groupheader;
- }
- }
-
- $form['actions']['#type'] = 'actions';
- $form['actions']['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Update'),
- );
-
- return $form;
-}
-
-/**
- * Form submit handler for path alias bulk update form.
- *
- * @see pathauto_batch_update_form()
- * @see pathauto_bulk_update_batch_finished()
- */
-function pathauto_bulk_update_form_submit($form, &$form_state) {
- $batch = array(
- 'title' => t('Bulk updating URL aliases'),
- 'operations' => array(
- array('pathauto_bulk_update_batch_start', array()),
- ),
- 'finished' => 'pathauto_bulk_update_batch_finished',
- 'file' => drupal_get_path('module', 'pathauto') . '/pathauto.admin.inc',
- );
-
- foreach ($form_state['values']['update'] as $callback) {
- if (!empty($callback)) {
- $settings = $form['#update_callbacks'][$callback];
- if (!empty($settings->batch_file)) {
- $batch['operations'][] = array('pathauto_bulk_update_batch_process', array($callback, $settings));
- }
- else {
- $batch['operations'][] = array($callback, array());
- }
- }
- }
-
- batch_set($batch);
-}
-
-/**
- * Batch callback; count the current number of URL aliases for comparison later.
- */
-function pathauto_bulk_update_batch_start(&$context) {
- $context['results']['count_before'] = db_select('url_alias')->countQuery()->execute()->fetchField();
-}
-
-/**
- * Common batch processing callback for all operations.
- *
- * Required to load our include the proper batch file.
- */
-function pathauto_bulk_update_batch_process($callback, $settings, &$context) {
- if (!empty($settings->batch_file)) {
- require_once DRUPAL_ROOT . '/' . $settings->batch_file;
- }
- return $callback($context);
-}
-
-/**
- * Batch finished callback.
- */
-function pathauto_bulk_update_batch_finished($success, $results, $operations) {
- if ($success) {
- // Count the current number of URL aliases after the batch is completed
- // and compare to the count before the batch started.
- $results['count_after'] = db_select('url_alias')->countQuery()->execute()->fetchField();
- $results['count_changed'] = max($results['count_after'] - $results['count_before'], 0);
- if ($results['count_changed']) {
- drupal_set_message(format_plural($results['count_changed'], 'Generated 1 URL alias.', 'Generated @count URL aliases.'));
- }
- else {
- drupal_set_message(t('No new URL aliases to generate.'));
- }
- }
- else {
- $error_operation = reset($operations);
- drupal_set_message(t('An error occurred while processing @operation with arguments : @args', array('@operation' => $error_operation[0], '@args' => print_r($error_operation[0], TRUE))));
- }
-}
-
-/**
- * Menu callback; select certain alias types to delete.
- */
-function pathauto_admin_delete() {
- /* TODO:
- 1) all - DONE
- 2) all node aliases - DONE
- 4) all user aliases - DONE
- 5) all taxonomy aliases - DONE
- 6) by node type
- 7) by taxonomy vocabulary
- 8) no longer existing aliases (see http://drupal.org/node/128366 )
- 9) where src like 'pattern' - DON'T DO
- 10) where dst like 'pattern' - DON'T DO
- */
-
- $form['delete'] = array(
- '#type' => 'fieldset',
- '#title' => t('Choose aliases to delete'),
- '#collapsible' => FALSE,
- '#collapsed' => FALSE,
- );
-
- // First we do the "all" case
- $total_count = db_query('SELECT count(1) FROM {url_alias}')->fetchField();
- $form['delete']['all_aliases'] = array(
- '#type' => 'checkbox',
- '#title' => t('All aliases'),
- '#default_value' => FALSE,
- '#description' => t('Delete all aliases. Number of aliases which will be deleted: %count.', array('%count' => $total_count)),
- );
-
- // Next, iterate over an array of objects/alias types which can be deleted and provide checkboxes
- $objects = module_invoke_all('path_alias_types');
- foreach ($objects as $internal_name => $label) {
- $count = db_query("SELECT count(1) FROM {url_alias} WHERE source LIKE :src", array(':src' => "$internal_name%"))->fetchField();
- $form['delete'][$internal_name] = array(
- '#type' => 'checkbox',
- '#title' => $label, // This label is sent through t() in the hard coded function where it is defined
- '#default_value' => FALSE,
- '#description' => t('Delete aliases for all @label. Number of aliases which will be deleted: %count.', array('@label' => $label, '%count' => $count)),
- );
- }
-
- // Warn them and give a button that shows we mean business
- $form['warning'] = array('#value' => '
' . t('Note: there is no confirmation. Be sure of your action before clicking the "Delete aliases now!" button.
You may want to make a backup of the database and/or the url_alias table prior to using this feature.') . '
' . t('Provides a mechanism for modules to automatically generate aliases for the content they manage.') . '
'; $output .= '' . t('Bulk generation will only generate URL aliases for items that currently have no aliases. This is typically used when installing Pathauto on a site that has existing un-aliased content that needs to be aliased in bulk.') . '
'; return $output; } } /** - * Implements hook_permission(). + * Implements hook__entity_bundle_delete(). */ -function pathauto_permission() { - return array( - 'administer pathauto' => array( - 'title' => t('Administer pathauto'), - 'description' => t('Allows a user to configure patterns for automated aliases and bulk delete URL-aliases.'), - ), - 'notify of path changes' => array( - 'title' => t('Notify of Path Changes'), - 'description' => t('Determines whether or not users are notified.'), - ), - ); +function pathauto_entity_bundle_delete($entity_type, $bundle) { + $config = \Drupal::configFactory()->getEditable('pathauto.pattern'); + $config->clear('patterns.' . $entity_type . '.bundles.' . $bundle); + $config->save(); } -/** - * Implements hook_menu(). - */ -function pathauto_menu() { - $items['admin/config/search/path/patterns'] = array( - 'title' => 'Patterns', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('pathauto_patterns_form'), - 'access arguments' => array('administer pathauto'), - 'type' => MENU_LOCAL_TASK, - 'weight' => 10, - 'file' => 'pathauto.admin.inc', - ); - $items['admin/config/search/path/settings'] = array( - 'title' => 'Settings', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('pathauto_settings_form'), - 'access arguments' => array('administer pathauto'), - 'type' => MENU_LOCAL_TASK, - 'weight' => 20, - 'file' => 'pathauto.admin.inc', - ); - $items['admin/config/search/path/update_bulk'] = array( - 'title' => 'Bulk generate', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('pathauto_bulk_update_form'), - 'access arguments' => array('administer url aliases'), - 'type' => MENU_LOCAL_TASK, - 'weight' => 30, - 'file' => 'pathauto.admin.inc', - ); - $items['admin/config/search/path/delete_bulk'] = array( - 'title' => 'Delete aliases', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('pathauto_admin_delete'), - 'access arguments' => array('administer url aliases'), - 'type' => MENU_LOCAL_TASK, - 'weight' => 40, - 'file' => 'pathauto.admin.inc', - ); - - return $items; -} /** - * Load an URL alias pattern by entity, bundle, and language. - * - * @param $entity - * An entity (e.g. node, taxonomy, user, etc.) - * @param $bundle - * A bundle (e.g. content type, vocabulary ID, etc.) - * @param $language - * A language code, defaults to the LANGUAGE_NONE constant. - */ -function pathauto_pattern_load_by_entity($entity, $bundle = '', $language = LANGUAGE_NONE) { - $patterns = &drupal_static(__FUNCTION__, array()); - - $pattern_id = "$entity:$bundle:$language"; - if (!isset($patterns[$pattern_id])) { - $variables = array(); - if ($language != LANGUAGE_NONE) { - $variables[] = "pathauto_{$entity}_{$bundle}_{$language}_pattern"; - } - if ($bundle) { - $variables[] = "pathauto_{$entity}_{$bundle}_pattern"; - } - $variables[] = "pathauto_{$entity}_pattern"; - - foreach ($variables as $variable) { - if ($pattern = trim(variable_get($variable, ''))) { - break; - } - } - - $patterns[$pattern_id] = $pattern; - } - - return $patterns[$pattern_id]; -} - -/** - * Delete multiple URL aliases. - * - * Intent of this is to abstract a potential path_delete_multiple() function - * for Drupal 7 or 8. - * - * @param $pids - * An array of path IDs to delete. - */ -function pathauto_path_delete_multiple($pids) { - foreach ($pids as $pid) { - path_delete(array('pid' => $pid)); - } -} - -/** - * Delete an URL alias and any of its sub-paths. - * - * Given a source like 'node/1' this function will delete any alias that have - * that specific source or any sources that match 'node/1/%'. - * - * @param $source - * An string with a source URL path. - */ -function pathauto_path_delete_all($source) { - $sql = "SELECT pid FROM {url_alias} WHERE source = :source OR source LIKE :source_wildcard"; - $pids = db_query($sql, array(':source' => $source, ':source_wildcard' => $source . '/%'))->fetchCol(); - if ($pids) { - pathauto_path_delete_multiple($pids); - } -} - -/** - * Delete an entity URL alias and any of its sub-paths. - * - * This function also checks to see if the default entity URI is different from - * the current entity URI and will delete any of the default aliases. - * - * @param $entity_type - * A string with the entity type. - * @param $entity - * An entity object. - * @param $default_uri - * The optional default uri path for the entity. - */ -function pathauto_entity_path_delete_all($entity_type, $entity, $default_uri = NULL) { - $uri = entity_uri($entity_type, $entity); - pathauto_path_delete_all($uri['path']); - if (isset($default_uri) && $uri['path'] != $default_uri) { - pathauto_path_delete_all($default_uri); - } -} - -/** - * Implements hook_field_attach_rename_bundle(). - * - * Respond to machine name changes for pattern variables. - */ -function pathauto_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { - $variables = db_select('variable', 'v') - ->fields('v', array('name')) - ->condition('name', db_like("pathauto_{$entity_type}_{$bundle_old}_") . '%', 'LIKE') - ->execute() - ->fetchCol(); - foreach ($variables as $variable) { - $value = variable_get($variable, ''); - variable_del($variable); - $variable = strtr($variable, array("{$entity_type}_{$bundle_old}" => "{$entity_type}_{$bundle_new}")); - variable_set($variable, $value); - } -} - -/** - * Implements hook_field_attach_delete_bundle(). - * - * Respond to sub-types being deleted, their patterns can be removed. - */ -function pathauto_field_attach_delete_bundle($entity_type, $bundle) { - $variables = db_select('variable', 'v') - ->fields('v', array('name')) - ->condition('name', db_like("pathauto_{$entity_type}_{$bundle}_") . '%', 'LIKE') - ->execute() - ->fetchCol(); - foreach ($variables as $variable) { - variable_del($variable); - } -} - -/** - * Implements hook_field_attach_form(). - * - * Add the automatic alias form elements to an existing path form fieldset. + * Implements hook_entity_presave(). */ -function pathauto_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { - list($id, , $bundle) = entity_extract_ids($entity_type, $entity); - - if (!isset($form['path'])) { - // This entity must be supported by core's path.module first. - // @todo Investigate removing this and supporting all fieldable entities. - return; - } - else { - // Taxonomy terms do not have an actual fieldset for path settings. - // Merge in the defaults. - $form['path'] += array( - '#type' => 'fieldset', - '#title' => t('URL path settings'), - '#collapsible' => TRUE, - '#collapsed' => empty($form['path']['alias']), - '#group' => 'additional_settings', - '#attributes' => array( - 'class' => array('path-form'), - ), - '#access' => user_access('create url aliases') || user_access('administer url aliases'), - '#weight' => 30, - '#tree' => TRUE, - '#element_validate' => array('path_form_element_validate'), - ); - } - - $pattern = pathauto_pattern_load_by_entity($entity_type, $bundle, $langcode); - if (empty($pattern)) { +function pathauto_entity_presave($entity) { + if (!($entity instanceof ContentEntityInterface) || $entity->hasField('path')) { return; } - - if (!isset($entity->path['pathauto'])) { - if (!empty($id)) { - module_load_include('inc', 'pathauto'); - $uri = entity_uri($entity_type, $entity); - $pathauto_alias = pathauto_create_alias($entity_type, 'return', $uri['path'], array($entity_type => $entity), $bundle, $langcode); - if ($pathauto_alias === FALSE) { - // If Pathauto is not going to be able to generate an alias, then we - // should not bother to show the checkbox since it wouldn't do anything. - // Note that if a pattern does apply, but all the tokens currently - // evaluate to empty strings, then $pathauto_alias would equal null and - // not false. - return; - } - else { - $path = drupal_get_path_alias($uri['path'], $langcode); - $entity->path['pathauto'] = ($path != $uri['path'] && $path == $pathauto_alias); - } - } - else { - $entity->path['pathauto'] = TRUE; - } - } - - // Add JavaScript that will disable the path textfield when the automatic - // alias checkbox is checked. - $form['path']['alias']['#states']['!enabled']['input[name="path[pathauto]"]'] = array('checked' => TRUE); - - // Override path.module's vertical tabs summary. - $form['path']['#attached']['js'] = array( - 'vertical-tabs' => drupal_get_path('module', 'pathauto') . '/pathauto.js' - ); - - $form['path']['pathauto'] = array( - '#type' => 'checkbox', - '#title' => t('Generate automatic URL alias'), - '#default_value' => $entity->path['pathauto'], - '#description' => t('Uncheck this to create a custom alias below.'), - '#weight' => -1, - ); - - // Add a shortcut link to configure URL alias patterns. - if (drupal_valid_path('admin/config/search/path/patterns')) { - $form['path']['pathauto']['#description'] .= ' ' . l(t('Configure URL alias patterns.'), 'admin/config/search/path/patterns'); - } - - if ($entity->path['pathauto'] && !empty($entity->old_alias) && empty($entity->path['alias'])) { - $form['path']['alias']['#default_value'] = $entity->old_alias; - $entity->path['alias'] = $entity->old_alias; - } - - // For Pathauto to remember the old alias and prevent the Path module from - // deleting it when Pathauto wants to preserve it. - if (!empty($entity->path['alias'])) { - $form['path']['old_alias'] = array( - '#type' => 'value', - '#value' => $entity->path['alias'], - ); - } -} - -/** - * Implements hook_entity_presave(). - */ -function pathauto_entity_presave($entity, $type) { // About to be saved (before insert/update) - if (!empty($entity->path['pathauto']) && isset($entity->path['old_alias']) - && $entity->path['alias'] == '' && $entity->path['old_alias'] != '') { - /** + if (!empty($entity->path->pathauto) && isset($entity->path->old_alias) + && $entity->path->alias == '' && $entity->path->old_alias != '') { + /* * There was an old alias, but when pathauto_perform_alias was checked * the javascript disabled the textbox which led to an empty value being * submitted. Restoring the old path-value here prevents the Path module * from deleting any old alias before Pathauto gets control. */ - $entity->path['alias'] = $entity->path['old_alias']; + $entity->path->alias = $entity->path->old_alias; } // Help prevent errors with progromatically creating entities by defining // path['alias'] as an empty string. // @see http://drupal.org/node/1328180 // @see http://drupal.org/node/1576552 - if (isset($entity->path['pathauto']) && !isset($entity->path['alias'])) { - $entity->path['alias'] = ''; - } -} - -/** - * Implements hook_action_info(). - */ -function pathauto_action_info() { - $info['pathauto_node_update_action'] = array( - 'type' => 'node', - 'label' => t('Update node alias'), - 'configurable' => FALSE, - 'triggers' => array(), - ); - $info['pathauto_taxonomy_term_update_action'] = array( - 'type' => 'taxonomy_term', - 'label' => t('Update taxonomy term alias'), - 'configurable' => FALSE, - 'triggers' => array(), - ); - $info['pathauto_user_update_action'] = array( - 'type' => 'user', - 'label' => t('Update user alias'), - 'configurable' => FALSE, - 'triggers' => array(), - ); - - return $info; -} - -/** - * Returns the language code of the given entity. - * - * Backward compatibility layer to ensure that installations running an older - * version of core where entity_language() is not available do not break. - * - * @param string $entity_type - * An entity type. - * @param object $entity - * An entity object. - * @param bool $check_language_property - * A boolean if TRUE, will attempt to fetch the language code from - * $entity->language if the entity_language() function failed or does not - * exist. Default is TRUE. - */ -function pathauto_entity_language($entity_type, $entity, $check_language_property = TRUE) { - $langcode = NULL; - - if (function_exists('entity_language')) { - $langcode = entity_language($entity_type, $entity); - } - elseif ($check_language_property && !empty($entity->language)) { - $langcode = $entity->language; - } - - return !empty($langcode) ? $langcode : LANGUAGE_NONE; -} - -function pathauto_is_alias_reserved($alias, $source, $langcode = LANGUAGE_NONE) { - foreach (module_implements('pathauto_is_alias_reserved') as $module) { - $result = module_invoke($module, 'pathauto_is_alias_reserved', $alias, $source, $langcode); - if (!empty($result)) { - // As soon as the first module says that an alias is in fact reserved, - // then there is no point in checking the rest of the modules. - return TRUE; - } - } - - return FALSE; -} - -/** - * Implements hook_pathauto_is_alias_reserved() on behalf of path.module. - */ -function path_pathauto_is_alias_reserved($alias, $source, $langcode) { - // For language neutral content, we need to make sure the alias doesn't - // collide with any existing aliases. For localized content, just make sure - // it doesn't collide with same language or language neutral aliases. - $query = db_select('url_alias', 'ua') - ->fields('ua', array('pid')) - ->condition('source', $source, '<>') - ->condition('alias', $alias); - - if ($langcode != LANGUAGE_NONE) { - $query->condition('language', array($langcode, LANGUAGE_NONE), 'IN'); - } - - return $query->execute()->rowCount() > 0; -} - -/** - * Implements hook_pathauto_is_alias_reserved(). - */ -function pathauto_pathauto_is_alias_reserved($alias, $source, $langcode) { - module_load_include('inc', 'pathauto'); - return _pathauto_path_is_callback($alias); -} - -if (!function_exists('path_field_extra_fields')) { -/** - * Implements hook_field_extra_fields() on behalf of path.module. - * - * Add support for the 'URL path settings' to be re-ordered by the user on the - * 'Manage Fields' tab of content types and vocabularies. - */ -function path_field_extra_fields() { - $info = array(); - - foreach (node_type_get_types() as $node_type) { - if (!isset($info['node'][$node_type->type]['form']['path'])) { - $info['node'][$node_type->type]['form']['path'] = array( - 'label' => t('URL path settings'), - 'description' => t('Path module form elements'), - 'weight' => 30, - ); - } - } - - if (module_exists('taxonomy')) { - $vocabularies = taxonomy_get_vocabularies(); - foreach ($vocabularies as $vocabulary) { - if (!isset($info['taxonomy_term'][$vocabulary->machine_name]['form']['path'])) { - $info['taxonomy_term'][$vocabulary->machine_name]['form']['path'] = array( - 'label' => t('URL path settings'), - 'description' => t('Path module form elements'), - 'weight' => 30, - ); - } - } - } - - return $info; -} -} - -/** - * @name pathauto_node Pathauto integration for the core node module. - * @{ - */ - -/** - * Implements hook_path_alias_types() on behalf of node module. - */ -function node_path_alias_types() { - return array('node/' => t('Content')); -} - -/** - * Implements hook_pathauto() on behalf of node module. - */ -function node_pathauto($op) { - if ($op == 'settings') { - $settings = array(); - $settings['module'] = 'node'; - $settings['token_type'] = 'node'; - $settings['groupheader'] = t('Content paths'); - $settings['patterndescr'] = t('Default path pattern (applies to all content types with blank patterns below)'); - $settings['patterndefault'] = 'content/[node:title]'; - $settings['batch_update_callback'] = 'node_pathauto_bulk_update_batch_process'; - $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc'; - - $languages = array(); - if (module_exists('locale')) { - $languages = array(LANGUAGE_NONE => t('language neutral')) + locale_language_list('name'); - } - - foreach (node_type_get_names() as $node_type => $node_name) { - if (count($languages) && variable_get('language_content_type_' . $node_type, 0)) { - $settings['patternitems'][$node_type] = t('Default path pattern for @node_type (applies to all @node_type content types with blank patterns below)', array('@node_type' => $node_name)); - foreach ($languages as $lang_code => $lang_name) { - $settings['patternitems'][$node_type . '_' . $lang_code] = t('Pattern for all @language @node_type paths', array('@node_type' => $node_name, '@language' => $lang_name)); - } - } - else { - $settings['patternitems'][$node_type] = t('Pattern for all @node_type paths', array('@node_type' => $node_name)); - } - } - return (object) $settings; - } -} - -/** - * Implements hook_node_insert(). - */ -function pathauto_node_insert($node) { - // @todo Remove the next line when http://drupal.org/node/1025870 is fixed. - unset($node->uri); - pathauto_node_update_alias($node, 'insert'); -} - -/** - * Implements hook_node_update(). - */ -function pathauto_node_update($node) { - pathauto_node_update_alias($node, 'update'); -} - -/** - * Implements hook_node_delete(). - */ -function pathauto_node_delete($node) { - pathauto_entity_path_delete_all('node', $node, "node/{$node->nid}"); -} - -/** - * Implements hook_form_BASE_FORM_ID_alter(). - * - * Add the Pathauto settings to the node form. - */ -function pathauto_form_node_form_alter(&$form, &$form_state) { - $node = $form_state['node']; - $langcode = pathauto_entity_language('node', $node); - pathauto_field_attach_form('node', $node, $form, $form_state, $langcode); -} - -/** - * Implements hook_node_operations(). - */ -function pathauto_node_operations() { - $operations['pathauto_update_alias'] = array( - 'label' => t('Update URL alias'), - 'callback' => 'pathauto_node_update_alias_multiple', - 'callback arguments' => array('bulkupdate', array('message' => TRUE)), - ); - return $operations; -} - -/** - * Update the URL aliases for an individual node. - * - * @param $node - * A node object. - * @param $op - * Operation being performed on the node ('insert', 'update' or 'bulkupdate'). - * @param $options - * An optional array of additional options. - */ -function pathauto_node_update_alias(stdClass $node, $op, array $options = array()) { - // Skip processing if the user has disabled pathauto for the node. - if (isset($node->path['pathauto']) && empty($node->path['pathauto']) && empty($options['force'])) { - return FALSE; - } - - $options += array('language' => pathauto_entity_language('node', $node)); - - // Skip processing if the node has no pattern. - if (!pathauto_pattern_load_by_entity('node', $node->type, $options['language'])) { - return FALSE; - } - - module_load_include('inc', 'pathauto'); - $uri = entity_uri('node', $node); - return pathauto_create_alias('node', $op, $uri['path'], array('node' => $node), $node->type, $options['language']); -} - -/** - * Update the URL aliases for multiple nodes. - * - * @param $nids - * An array of node IDs. - * @param $op - * Operation being performed on the nodes ('insert', 'update' or - * 'bulkupdate'). - * @param $options - * An optional array of additional options. - */ -function pathauto_node_update_alias_multiple(array $nids, $op, array $options = array()) { - $options += array('message' => FALSE); - - $nodes = node_load_multiple($nids); - foreach ($nodes as $node) { - pathauto_node_update_alias($node, $op, $options); - } - - if (!empty($options['message'])) { - drupal_set_message(format_plural(count($nids), 'Updated URL alias for 1 node.', 'Updated URL aliases for @count nodes.')); - } -} - -/** - * Update action wrapper for pathauto_node_update_alias(). - */ -function pathauto_node_update_action($node, $context = array()) { - pathauto_node_update_alias($node, 'bulkupdate', array('message' => TRUE)); -} - -/** - * @} End of "name pathauto_node". - */ - -/** - * @name pathauto_taxonomy Pathauto integration for the core taxonomy module. - * @{ - */ - -/** - * Implements hook_path_alias_types() on behalf of taxonomy module. - */ -function taxonomy_path_alias_types() { - return array('taxonomy/term/' => t('Taxonomy terms')); -} - -/** - * Implements hook_pathauto() on behalf of taxonomy module. - */ -function taxonomy_pathauto($op) { - if ($op == 'settings') { - $settings = array(); - $settings['module'] = 'taxonomy_term'; - $settings['token_type'] = 'term'; - $settings['groupheader'] = t('Taxonomy term paths'); - $settings['patterndescr'] = t('Default path pattern (applies to all vocabularies with blank patterns below)'); - $settings['patterndefault'] = '[term:vocabulary]/[term:name]'; - $settings['batch_update_callback'] = 'taxonomy_pathauto_bulk_update_batch_process'; - $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc'; - - $vocabularies = taxonomy_get_vocabularies(); - if (count($vocabularies)) { - $settings['patternitems'] = array(); - foreach ($vocabularies as $vid => $vocabulary) { - if ($vid == variable_get('forum_nav_vocabulary', '')) { - // Skip the forum vocabulary. - continue; - } - $settings['patternitems'][$vocabulary->machine_name] = t('Pattern for all %vocab-name paths', array('%vocab-name' => $vocabulary->name)); - } - } - return (object) $settings; - } -} - -/** - * Implements hook_taxonomy_term_insert(). - */ -function pathauto_taxonomy_term_insert($term) { - pathauto_taxonomy_term_update_alias($term, 'insert'); -} - -/** - * Implements hook_taxonomy_term_update(). - */ -function pathauto_taxonomy_term_update($term) { - pathauto_taxonomy_term_update_alias($term, 'update', array('alias children' => TRUE)); -} - -/** - * Implements hook_taxonomy_term_delete(). - */ -function pathauto_taxonomy_term_delete($term) { - pathauto_entity_path_delete_all('taxonomy_term', $term, "taxonomy/term/{$term->tid}"); -} - -/** - * Implements hook_form_FORM_ID_alter(). - * - * Add the Pathauto settings to the taxonomy term form. - */ -function pathauto_form_taxonomy_form_term_alter(&$form, $form_state) { - $term = $form_state['term']; - $langcode = pathauto_entity_language('taxonomy_term', $term); - pathauto_field_attach_form('taxonomy_term', $term, $form, $form_state, $langcode); -} - -/** - * Update the URL aliases for an individual taxonomy term. - * - * @param $term - * A taxonomy term object. - * @param $op - * Operation being performed on the term ('insert', 'update' or 'bulkupdate'). - * @param $options - * An optional array of additional options. - */ -function pathauto_taxonomy_term_update_alias(stdClass $term, $op, array $options = array()) { - // Skip processing if the user has disabled pathauto for the term. - if (isset($term->path['pathauto']) && empty($term->path['pathauto']) && empty($options['force'])) { - return FALSE; - } - - $module = 'taxonomy_term'; - if ($term->vid == variable_get('forum_nav_vocabulary', '')) { - if (module_exists('forum')) { - $module = 'forum'; - } - else { - return FALSE; - } - } - - // Check that the term has its bundle, which is the vocabulary's machine name. - if (!isset($term->vocabulary_machine_name)) { - $vocabulary = taxonomy_vocabulary_load($term->vid); - $term->vocabulary_machine_name = $vocabulary->machine_name; - } - - $options += array( - 'alias children' => FALSE, - 'language' => pathauto_entity_language('taxonomy_term', $term), - ); - - // Skip processing if the term has no pattern. - if (!pathauto_pattern_load_by_entity($module, $term->vocabulary_machine_name)) { - return FALSE; - } - - module_load_include('inc', 'pathauto'); - $uri = entity_uri('taxonomy_term', $term); - $result = pathauto_create_alias($module, $op, $uri['path'], array('term' => $term), $term->vocabulary_machine_name, $options['language']); - - if (!empty($options['alias children'])) { - // For all children generate new aliases. - unset($options['language']); - foreach (taxonomy_get_children($term->tid, $term->vid) as $subterm) { - pathauto_taxonomy_term_update_alias($subterm, $op, $options); - } - } - - return $result; -} - -/** - * Update the URL aliases for multiple taxonomy terms. - * - * @param $tids - * An array of term IDs. - * @param $op - * Operation being performed on the nodes ('insert', 'update' or - * 'bulkupdate'). - * @param $options - * An optional array of additional options. - */ -function pathauto_taxonomy_term_update_alias_multiple(array $tids, $op, array $options = array()) { - $options += array('message' => FALSE); - - $terms = taxonomy_term_load_multiple($tids); - foreach ($terms as $term) { - pathauto_taxonomy_term_update_alias($term, $op, $options); - } - - if (!empty($options['message'])) { - drupal_set_message(format_plural(count($tids), 'Updated URL alias for 1 term.', 'Updated URL aliases for @count terms.')); + if (isset($entity->path->pathauto) && !isset($entity->path->alias)) { + $entity->path->alias = ''; } } /** - * Update action wrapper for pathauto_taxonomy_term_update_alias(). + * Implements hook_entity_insert(). */ -function pathauto_taxonomy_term_update_action($term, $context = array()) { - pathauto_taxonomy_term_update_alias($term, 'bulkupdate', array('message' => TRUE)); +function pathauto_entity_insert(EntityInterface $entity) { + \Drupal::service('pathauto.manager')->updateAlias($entity, 'insert'); } /** - * @} End of "name pathauto_taxonomy". - */ - -/** - * @name pathauto_forum Pathauto integration for the core forum module. - * @{ + * Implements hook_entity_update(). */ - -/** - * Implements hook_path_alias_types() on behalf of forum module. - */ -function forum_path_alias_types() { - return array('forum/' => t('Forums')); +function pathauto_entity_update(EntityInterface $entity) { + \Drupal::service('pathauto.manager')->updateAlias($entity, 'update'); } -/** - * Implements hook_pathauto() for forum module. - */ -function forum_pathauto($op) { - if ($op == 'settings') { - $settings = array(); - $settings['module'] = 'forum'; - $settings['token_type'] = 'term'; - $settings['groupheader'] = t('Forum paths'); - $settings['patterndescr'] = t('Pattern for forums and forum containers'); - $settings['patterndefault'] = '[term:vocabulary]/[term:name]'; - $settings['batch_update_callback'] = 'forum_pathauto_bulk_update_batch_process'; - $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc'; - return (object) $settings; - } -} - -/** - * @} End of "name pathauto_forum". - */ - -/** - * @name pathauto_user Pathauto integration for the core user and blog modules. - * @{ - */ - -/** - * Implements hook_path_alias_types() on behalf of user module. - */ -function user_path_alias_types() { - return array('user/' => t('Users')); -} /** - * Implements hook_pathauto() on behalf of user module. + * Implements hook_entity_update(). */ -function user_pathauto($op) { - if ($op == 'settings') { - $settings = array(); - $settings['module'] = 'user'; - $settings['token_type'] = 'user'; - $settings['groupheader'] = t('User paths'); - $settings['patterndescr'] = t('Pattern for user account page paths'); - $settings['patterndefault'] = 'users/[user:name]'; - $settings['batch_update_callback'] = 'user_pathauto_bulk_update_batch_process'; - $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc'; - return (object) $settings; +function pathauto_entity_delete(EntityInterface $entity) { + if ($entity->hasLinkTemplate('canonical')) { + \Drupal::service('pathauto.alias_storage_helper')->deleteEntityPathAll($entity); } } /** - * Implements hook_user_insert(). + * Implements hook_field_info_alter(). */ -function pathauto_user_insert(&$edit, $account, $category) { - pathauto_user_update_alias($account, 'insert'); +function pathauto_field_info_alter(&$info) { + $info['path']['class'] = '\Drupal\pathauto\PathautoItem'; } /** - * Implements hook_user_update(). + * Implements hook_field_widget_info_alter(). */ -function pathauto_user_update(&$edit, $account, $category) { - pathauto_user_update_alias($account, 'update'); +function pathauto_field_widget_info_alter(&$widgets) { + $widgets['path']['class'] = 'Drupal\pathauto\PathautoWidget'; } /** - * Implements hook_user_delete(). + * Implements hook_entity_base_field_info(). */ -function pathauto_user_delete($account) { - pathauto_entity_path_delete_all('user', $account, "user/{$account->uid}"); - pathauto_path_delete_all("blog/{$account->uid}"); -} - -/** - * Implements hook_user_operations(). - */ -function pathauto_user_operations() { - $operations['pathauto_update_alias'] = array( - 'label' => t('Update URL alias'), - 'callback' => 'pathauto_user_update_alias_multiple', - 'callback arguments' => array('bulkupdate', array('message' => TRUE)), - ); - return $operations; -} - -/** - * Update the URL aliases for an individual user account. - * - * @param $account - * A user account object. - * @param $op - * Operation being performed on the account ('insert', 'update' or - * 'bulkupdate'). - * @param $options - * An optional array of additional options. - */ -function pathauto_user_update_alias(stdClass $account, $op, array $options = array()) { - // Skip processing if the user has disabled pathauto for the account. - if (isset($account->path['pathauto']) && empty($account->path['pathauto']) && empty($options['force'])) { - return FALSE; - } - - $options += array( - 'alias blog' => module_exists('blog'), - // $user->language is not the user entity language, thus we need to skip - // the property fallback check. - 'language' => pathauto_entity_language('user', $account, FALSE), - ); - - // Skip processing if the account has no pattern. - if (!pathauto_pattern_load_by_entity('user', '', $options['language'])) { - return FALSE; - } - - module_load_include('inc', 'pathauto'); - $uri = entity_uri('user', $account); - $return = pathauto_create_alias('user', $op, $uri['path'], array('user' => $account), NULL, $options['language']); - - // Because blogs are also associated with users, also generate the blog paths. - if (!empty($options['alias blog'])) { - pathauto_blog_update_alias($account, $op, $options); - } - - return $return; -} - -/** - * Update the URL aliases for multiple user accounts. - * - * @param $uids - * An array of user account IDs. - * @param $op - * Operation being performed on the accounts ('insert', 'update' or - * 'bulkupdate'). - * @param $options - * An optional array of additional options. - */ -function pathauto_user_update_alias_multiple(array $uids, $op, array $options = array()) { - $options += array('message' => FALSE); - - $accounts = user_load_multiple($uids); - foreach ($accounts as $account) { - pathauto_user_update_alias($account, $op, $options); - } - - if (!empty($options['message'])) { - drupal_set_message(format_plural(count($uids), 'Updated URL alias for 1 user account.', 'Updated URL aliases for @count user accounts.')); - } -} - -/** - * Update action wrapper for pathauto_user_update_alias(). - */ -function pathauto_user_update_action($account, $context = array()) { - pathauto_user_update_alias($account, 'bulkupdate', array('message' => TRUE)); -} - -/** - * @} End of "name pathauto_user". - */ - -/** - * @name pathauto_blog Pathauto integration for the core blog module. - * @{ - */ - -/** - * Implements hook_path_alias_types() on behalf of blog module. - */ -function blog_path_alias_types() { - return array('blog/' => t('User blogs')); -} - -/** - * Implements hook_pathauto() on behalf of blog module. - */ -function blog_pathauto($op) { - if ($op == 'settings') { - $settings = array(); - $settings['module'] = 'blog'; - $settings['token_type'] = 'user'; - $settings['groupheader'] = t('Blog paths'); - $settings['patterndescr'] = t('Pattern for blog page paths'); - $settings['patterndefault'] = 'blogs/[user:name]'; - $settings['batch_update_callback'] = 'blog_pathauto_bulk_update_batch_process'; - $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc'; - return (object) $settings; - } -} - -/** - * Update the blog URL aliases for an individual user account. - * - * @param $account - * A user account object. - * @param $op - * Operation being performed on the blog ('insert', 'update' or - * 'bulkupdate'). - * @param $options - * An optional array of additional options. - */ -function pathauto_blog_update_alias(stdClass $account, $op, array $options = array()) { - // Skip processing if the blog has no pattern. - if (!pathauto_pattern_load_by_entity('blog')) { - return FALSE; - } - - $options += array( - 'language' => LANGUAGE_NONE, - ); +function pathauto_entity_base_field_info(EntityTypeInterface $entity_type) { + // @todo: Make this configurable and/or remove if + // https://drupal.org/node/476294 is resolved. + if ($entity_type->id() === 'user') { + $fields['path'] = BaseFieldDefinition::create('path') + ->setCustomStorage(TRUE) + ->setLabel(t('URL alias')) + ->setTranslatable(TRUE) + ->setDisplayOptions('form', array( + 'type' => 'path', + 'weight' => 30, + )) + ->setDisplayConfigurable('form', TRUE); - module_load_include('inc', 'pathauto'); - if (node_access('create', 'blog', $account)) { - return pathauto_create_alias('blog', $op, "blog/{$account->uid}", array('user' => $account), NULL, $options['language']); - } - else { - pathauto_path_delete_all("blog/{$account->uid}"); + return $fields; } } - -/** - * @} End of "name pathauto_blog". - */ diff --git a/pathauto.pathauto.inc b/pathauto.pathauto.inc deleted file mode 100644 index 23df259..0000000 --- a/pathauto.pathauto.inc +++ /dev/null @@ -1,227 +0,0 @@ -leftJoin('url_alias', 'ua', "CONCAT('node/', n.nid) = ua.source"); - $query->addField('n', 'nid'); - $query->isNull('ua.source'); - $query->condition('n.nid', $context['sandbox']['current'], '>'); - $query->orderBy('n.nid'); - $query->addTag('pathauto_bulk_update'); - $query->addMetaData('entity', 'node'); - - // Get the total amount of items to process. - if (!isset($context['sandbox']['total'])) { - $context['sandbox']['total'] = $query->countQuery()->execute()->fetchField(); - - // If there are no nodes to update, the stop immediately. - if (!$context['sandbox']['total']) { - $context['finished'] = 1; - return; - } - } - - $query->range(0, 25); - $nids = $query->execute()->fetchCol(); - - pathauto_node_update_alias_multiple($nids, 'bulkupdate'); - $context['sandbox']['count'] += count($nids); - $context['sandbox']['current'] = max($nids); - $context['message'] = t('Updated alias for node @nid.', array('@nid' => end($nids))); - - if ($context['sandbox']['count'] != $context['sandbox']['total']) { - $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total']; - } -} - -/** - * Batch processing callback; Generate aliases for taxonomy terms. - */ -function taxonomy_pathauto_bulk_update_batch_process(&$context) { - if (!isset($context['sandbox']['current'])) { - $context['sandbox']['count'] = 0; - $context['sandbox']['current'] = 0; - } - - $query = db_select('taxonomy_term_data', 'td'); - $query->leftJoin('url_alias', 'ua', "CONCAT('taxonomy/term/', td.tid) = ua.source"); - $query->addField('td', 'tid'); - $query->isNull('ua.source'); - $query->condition('td.tid', $context['sandbox']['current'], '>'); - // Exclude the forums terms. - if ($forum_vid = variable_get('forum_nav_vocabulary', '')) { - $query->condition('td.vid', $forum_vid, '<>'); - } - $query->orderBy('td.tid'); - $query->addTag('pathauto_bulk_update'); - $query->addMetaData('entity', 'taxonomy_term'); - - // Get the total amount of items to process. - if (!isset($context['sandbox']['total'])) { - $context['sandbox']['total'] = $query->countQuery()->execute()->fetchField(); - - // If there are no nodes to update, the stop immediately. - if (!$context['sandbox']['total']) { - $context['finished'] = 1; - return; - } - } - - $query->range(0, 25); - $tids = $query->execute()->fetchCol(); - - pathauto_taxonomy_term_update_alias_multiple($tids, 'bulkupdate'); - $context['sandbox']['count'] += count($tids); - $context['sandbox']['current'] = max($tids); - $context['message'] = t('Updated alias for term @tid.', array('@tid' => end($tids))); - - if ($context['sandbox']['count'] != $context['sandbox']['total']) { - $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total']; - } -} - -/** - * Batch processing callback; Generate aliases for forums. - */ -function forum_pathauto_bulk_update_batch_process(&$context) { - if (!isset($context['sandbox']['current'])) { - $context['sandbox']['count'] = 0; - $context['sandbox']['current'] = 0; - } - - $query = db_select('taxonomy_term_data', 'td'); - $query->leftJoin('url_alias', 'ua', "CONCAT('forum/', td.tid) = ua.source"); - $query->addField('td', 'tid'); - $query->isNull('ua.source'); - $query->condition('td.tid', $context['sandbox']['current'], '>'); - $query->condition('td.vid', variable_get('forum_nav_vocabulary', '')); - $query->orderBy('td.tid'); - $query->addTag('pathauto_bulk_update'); - $query->addMetaData('entity', 'taxonomy_term'); - - // Get the total amount of items to process. - if (!isset($context['sandbox']['total'])) { - $context['sandbox']['total'] = $query->countQuery()->execute()->fetchField(); - - // If there are no nodes to update, the stop immediately. - if (!$context['sandbox']['total']) { - $context['finished'] = 1; - return; - } - } - - $query->range(0, 25); - $tids = $query->execute()->fetchCol(); - - pathauto_taxonomy_term_update_alias_multiple($tids, 'bulkupdate'); - $context['sandbox']['count'] += count($tids); - $context['sandbox']['current'] = max($tids); - $context['message'] = t('Updated alias for forum @tid.', array('@tid' => end($tids))); - - if ($context['sandbox']['count'] != $context['sandbox']['total']) { - $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total']; - } -} - -/** - * Batch processing callback; Generate aliases for users. - */ -function user_pathauto_bulk_update_batch_process(&$context) { - if (!isset($context['sandbox']['current'])) { - $context['sandbox']['count'] = 0; - $context['sandbox']['current'] = 0; - } - - $query = db_select('users', 'u'); - $query->leftJoin('url_alias', 'ua', "CONCAT('user/', u.uid) = ua.source"); - $query->addField('u', 'uid'); - $query->isNull('ua.source'); - $query->condition('u.uid', $context['sandbox']['current'], '>'); - $query->orderBy('u.uid'); - $query->addTag('pathauto_bulk_update'); - $query->addMetaData('entity', 'user'); - - // Get the total amount of items to process. - if (!isset($context['sandbox']['total'])) { - $context['sandbox']['total'] = $query->countQuery()->execute()->fetchField(); - - // If there are no nodes to update, the stop immediately. - if (!$context['sandbox']['total']) { - $context['finished'] = 1; - return; - } - } - - $query->range(0, 25); - $uids = $query->execute()->fetchCol(); - - pathauto_user_update_alias_multiple($uids, 'bulkupdate', array('alias blog' => FALSE)); - $context['sandbox']['count'] += count($uids); - $context['sandbox']['current'] = max($uids); - $context['message'] = t('Updated alias for user @uid.', array('@uid' => end($uids))); - - if ($context['sandbox']['count'] != $context['sandbox']['total']) { - $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total']; - } -} - -/** - * Batch processing callback; Generate aliases for blogs. - */ -function blog_pathauto_bulk_update_batch_process(&$context) { - if (!isset($context['sandbox']['current'])) { - $context['sandbox']['count'] = 0; - $context['sandbox']['current'] = 0; - } - - $query = db_select('users', 'u'); - $query->leftJoin('url_alias', 'ua', "CONCAT('blog/', u.uid) = ua.source"); - $query->addField('u', 'uid'); - $query->isNull('ua.source'); - $query->condition('u.uid', $context['sandbox']['current'], '>'); - $query->orderBy('u.uid'); - $query->addTag('pathauto_bulk_update'); - $query->addMetaData('entity', 'user'); - - // Get the total amount of items to process. - if (!isset($context['sandbox']['total'])) { - $context['sandbox']['total'] = $query->countQuery()->execute()->fetchField(); - - // If there are no nodes to update, the stop immediately. - if (!$context['sandbox']['total']) { - $context['finished'] = 1; - return; - } - } - - $query->range(0, 25); - $uids = $query->execute()->fetchCol(); - - $accounts = user_load_multiple($uids); - foreach ($accounts as $account) { - pathauto_blog_update_alias($account, 'bulkupdate'); - } - - $context['sandbox']['count'] += count($uids); - $context['sandbox']['current'] = max($uids); - $context['message'] = t('Updated alias for blog user @uid.', array('@uid' => end($uids))); - - if ($context['sandbox']['count'] != $context['sandbox']['total']) { - $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total']; - } -} diff --git a/pathauto.permissions.yml b/pathauto.permissions.yml new file mode 100644 index 0000000..ede9f28 --- /dev/null +++ b/pathauto.permissions.yml @@ -0,0 +1,6 @@ +administer pathauto: + title: 'Administer pathauto' + description: 'Allows a user to configure patterns for automated aliases and bulk delete URL-aliases.' +notify of path changes: + title: 'Notify of Path Changes' + description: 'Determines whether or not users are notified.' diff --git a/pathauto.routing.yml b/pathauto.routing.yml new file mode 100644 index 0000000..ecc5a7b --- /dev/null +++ b/pathauto.routing.yml @@ -0,0 +1,31 @@ +pathauto.patterns.form: + path: '/admin/config/search/path/patterns' + defaults: + _form: '\Drupal\pathauto\Form\PathautoPatternsForm' + _title: 'Patterns' + requirements: + _permission: 'administer pathauto' + +pathauto.settings.form: + path: '/admin/config/search/path/settings' + defaults: + _form: '\Drupal\pathauto\Form\PathautoSettingsForm' + _title: 'Settings' + requirements: + _permission: 'administer pathauto' + +pathauto.bulk.update.form: + path: '/admin/config/search/path/update_bulk' + defaults: + _form: '\Drupal\pathauto\Form\PathautoBulkUpdateForm' + _title: 'Bulk generate' + requirements: + _permission: 'administer url aliases' + +pathauto.admin.delete: + path: '/admin/config/search/path/delete_bulk' + defaults: + _form: '\Drupal\pathauto\Form\PathautoAdminDelete' + _title: 'Delete aliases' + requirements: + _permission: 'administer url aliases' \ No newline at end of file diff --git a/pathauto.services.yml b/pathauto.services.yml new file mode 100644 index 0000000..dfd5cea --- /dev/null +++ b/pathauto.services.yml @@ -0,0 +1,19 @@ +services: + pathauto.manager: + class: Drupal\pathauto\PathautoManager + arguments: ['@config.factory', '@module_handler', '@token', '@pathauto.alias_cleaner', '@pathauto.alias_storage_helper', '@pathauto.alias_uniquifier', ,'@pathauto.verbose_messenger', '@string_translation'] + pathauto.alias_cleaner: + class: Drupal\pathauto\AliasCleaner + arguments: ['@config.factory', '@pathauto.alias_storage_helper', '@language_manager', '@cache.discovery', '@transliteration', '@module_handler'] + pathauto.alias_storage_helper: + class: Drupal\pathauto\AliasStorageHelper + arguments: ['@config.factory', '@path.alias_storage', '@database','@pathauto.verbose_messenger', '@string_translation'] + pathauto.alias_uniquifier: + class: Drupal\pathauto\AliasUniquifier + arguments: ['@config.factory', '@pathauto.alias_storage_helper','@module_handler', '@router.no_access_checks', '@path.alias_manager'] + pathauto.verbose_messenger: + class: Drupal\pathauto\VerboseMessenger + arguments: ['@config.factory', '@current_user'] + plugin.manager.alias_type: + class: Drupal\pathauto\AliasTypeManager + parent: default_plugin_manager diff --git a/pathauto.test b/pathauto.test deleted file mode 100644 index ab460f5..0000000 --- a/pathauto.test +++ /dev/null @@ -1,894 +0,0 @@ - $token), array($type => $object)); - $tokens += array($token => ''); - $this->assertIdentical($tokens[$token], $expected, t("Token value for [@type:@token] was '@actual', expected value '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $tokens[$token], '@expected' => $expected))); - } - - function saveAlias($source, $alias, $language = LANGUAGE_NONE) { - $alias = array( - 'source' => $source, - 'alias' => $alias, - 'language' => $language, - ); - path_save($alias); - return $alias; - } - - function saveEntityAlias($entity_type, $entity, $alias, $language = LANGUAGE_NONE) { - $uri = entity_uri($entity_type, $entity); - return $this->saveAlias($uri['path'], $alias, $language); - } - - function assertEntityAlias($entity_type, $entity, $expected_alias, $language = LANGUAGE_NONE) { - $uri = entity_uri($entity_type, $entity); - $this->assertAlias($uri['path'], $expected_alias, $language); - } - - function assertEntityAliasExists($entity_type, $entity) { - $uri = entity_uri($entity_type, $entity); - return $this->assertAliasExists(array('source' => $uri['path'])); - } - - function assertNoEntityAlias($entity_type, $entity, $language = LANGUAGE_NONE) { - $uri = entity_uri($entity_type, $entity); - $this->assertEntityAlias($entity_type, $entity, $uri['path'], $language); - } - - function assertNoEntityAliasExists($entity_type, $entity) { - $uri = entity_uri($entity_type, $entity); - $this->assertNoAliasExists(array('source' => $uri['path'])); - } - - function assertAlias($source, $expected_alias, $language = LANGUAGE_NONE) { - drupal_clear_path_cache($source); - $alias = drupal_get_path_alias($source, $language); - $this->assertIdentical($alias, $expected_alias, t("Alias for %source with language '@language' was %actual, expected %expected.", array('%source' => $source, '%actual' => $alias, '%expected' => $expected_alias, '@language' => $language))); - } - - function assertAliasExists($conditions) { - $path = path_load($conditions); - $this->assertTrue($path, t('Alias with conditions @conditions found.', array('@conditions' => var_export($conditions, TRUE)))); - return $path; - } - - function assertNoAliasExists($conditions) { - $alias = path_load($conditions); - $this->assertFalse($alias, t('Alias with conditions @conditions not found.', array('@conditions' => var_export($conditions, TRUE)))); - } - - function deleteAllAliases() { - db_delete('url_alias')->execute(); - drupal_clear_path_cache(); - } - - function addVocabulary(array $vocabulary = array()) { - $name = drupal_strtolower($this->randomName(5)); - $vocabulary += array( - 'name' => $name, - 'machine_name' => $name, - 'nodes' => array('article' => 'article'), - ); - $vocabulary = (object) $vocabulary; - taxonomy_vocabulary_save($vocabulary); - return $vocabulary; - } - - function addTerm(stdClass $vocabulary, array $term = array()) { - $term += array( - 'name' => drupal_strtolower($this->randomName(5)), - 'vocabulary_machine_name' => $vocabulary->machine_name, - 'vid' => $vocabulary->vid, - ); - $term = (object) $term; - taxonomy_term_save($term); - return $term; - } - - function assertEntityPattern($entity_type, $bundle, $language = LANGUAGE_NONE, $expected) { - drupal_static_reset('pathauto_pattern_load_by_entity'); - $this->refreshVariables(); - $pattern = pathauto_pattern_load_by_entity($entity_type, $bundle, $language); - $this->assertIdentical($expected, $pattern); - } - - function drupalGetTermByName($name, $reset = FALSE) { - $terms = entity_load('taxonomy_term', array(), array('name' => $name), $reset); - return !empty($terms) ? reset($terms) : FALSE; - } -} - -/** - * Unit tests for Pathauto functions. - */ -class PathautoUnitTestCase extends PathautoTestHelper { - public static function getInfo() { - return array( - 'name' => 'Pathauto unit tests', - 'description' => 'Unit tests for Pathauto functions.', - 'group' => 'Pathauto', - 'dependencies' => array('token'), - ); - } - - function setUp(array $modules = array()) { - parent::setUp($modules); - module_load_include('inc', 'pathauto'); - } - - /** - * Test _pathauto_get_schema_alias_maxlength(). - */ - function testGetSchemaAliasMaxLength() { - $this->assertIdentical(_pathauto_get_schema_alias_maxlength(), 255); - } - - /** - * Test pathauto_pattern_load_by_entity(). - */ - function testPatternLoadByEntity() { - variable_set('pathauto_node_story_en_pattern', ' story/en/[node:title] '); - variable_set('pathauto_node_story_pattern', 'story/[node:title]'); - variable_set('pathauto_node_pattern', 'content/[node:title]'); - variable_set('pathauto_user_pattern', 'users/[user:name]'); - - $tests = array( - array('entity' => 'node', 'bundle' => 'story', 'language' => 'fr', 'expected' => 'story/[node:title]'), - array('entity' => 'node', 'bundle' => 'story', 'language' => 'en', 'expected' => 'story/en/[node:title]'), - array('entity' => 'node', 'bundle' => 'story', 'language' => LANGUAGE_NONE, 'expected' => 'story/[node:title]'), - array('entity' => 'node', 'bundle' => 'page', 'language' => 'en', 'expected' => 'content/[node:title]'), - array('entity' => 'user', 'bundle' => 'user', 'language' => LANGUAGE_NONE, 'expected' => 'users/[user:name]'), - array('entity' => 'invalid-entity', 'bundle' => '', 'language' => LANGUAGE_NONE, 'expected' => ''), - ); - foreach ($tests as $test) { - $actual = pathauto_pattern_load_by_entity($test['entity'], $test['bundle'], $test['language']); - $this->assertIdentical($actual, $test['expected'], t("pathauto_pattern_load_by_entity('@entity', '@bundle', '@language') returned '@actual', expected '@expected'", array('@entity' => $test['entity'], '@bundle' => $test['bundle'], '@language' => $test['language'], '@actual' => $actual, '@expected' => $test['expected']))); - } - } - - /** - * Test pathauto_cleanstring(). - */ - function testCleanString() { - $tests = array(); - variable_set('pathauto_ignore_words', ', in, is,that, the , this, with, '); - variable_set('pathauto_max_component_length', 35); - - // Test the 'ignored words' removal. - $tests['this'] = 'this'; - $tests['this with that'] = 'this-with-that'; - $tests['this thing with that thing'] = 'thing-thing'; - - // Test length truncation and duplicate separator removal. - $tests[' - Pathauto is the greatest - module ever in Drupal history - '] = 'pathauto-greatest-module-ever'; - - // Test that HTML tags are removed. - $tests['This text has' . t('Note: there is no confirmation. Be sure of your action before clicking the "Delete aliases now!" button.
You may want to make a backup of the database and/or the url_alias table prior to using this feature.') . '
' . SafeMarkup::checkPlain($details['value']) . '
)',
+ '#default_value' => $details['default'],
+ '#options' => array(
+ PathautoManagerInterface::PUNCTUATION_REMOVE => t('Remove'),
+ PathautoManagerInterface::PUNCTUATION_REPLACE => t('Replace by separator'),
+ PathautoManagerInterface::PUNCTUATION_DO_NOTHING => t('No action (do not replace)'),
+ ),
+ );
+ }
+
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+
+ $config = $this->config('pathauto.settings');
+
+ $form_state->cleanValues();
+ foreach ($form_state->getValues() as $key => $value) {
+ $config->set($key, $value);
+ }
+
+ $config->save();
+
+ parent::submitForm($form, $form_state);
+ }
+
+}
diff --git a/src/MessengerInterface.php b/src/MessengerInterface.php
new file mode 100644
index 0000000..4e43319
--- /dev/null
+++ b/src/MessengerInterface.php
@@ -0,0 +1,25 @@
+pathauto) || empty($this->pathauto)) {
+ parent::insert();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function update() {
+ // Only allow the parent implementation to act if pathauto will not create
+ // an alias.
+ if (!isset($this->pathauto) || empty($this->pathauto)) {
+ parent::update();
+ }
+ }
+
+}
diff --git a/src/PathautoManager.php b/src/PathautoManager.php
new file mode 100644
index 0000000..9d5abf5
--- /dev/null
+++ b/src/PathautoManager.php
@@ -0,0 +1,329 @@
+configFactory = $config_factory;
+ $this->moduleHandler = $module_handler;
+ $this->token = $token;
+ $this->aliasCleaner = $alias_cleaner;
+ $this->aliasStorageHelper = $alias_storage_helper;
+ $this->aliasUniquifier = $alias_uniquifier;
+ $this->messenger = $messenger;
+ $this->stringTranslation = $string_translation;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createAlias($module, $op, $source, $data, $type = NULL, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED) {
+ $config = $this->configFactory->get('pathauto.settings');
+
+ // Retrieve and apply the pattern for this content type.
+ $pattern = $this->getPatternByEntity($module, $type, $langcode);
+
+ // Allow other modules to alter the pattern.
+ $context = array(
+ 'module' => $module,
+ 'op' => $op,
+ 'source' => $source,
+ 'data' => $data,
+ 'type' => $type,
+ 'language' => &$langcode,
+ );
+ $this->moduleHandler->alter('pathauto_pattern', $pattern, $context);
+
+ if (empty($pattern)) {
+ // No pattern? Do nothing (otherwise we may blow away existing aliases...)
+ return NULL;
+ }
+
+ // Special handling when updating an item which is already aliased.
+ $existing_alias = NULL;
+ if ($op == 'update' || $op == 'bulkupdate') {
+ if ($existing_alias = $this->aliasStorageHelper->loadBySource($source, $langcode)) {
+ switch ($config->get('update_action')) {
+ case PathautoManagerInterface::UPDATE_ACTION_NO_NEW:
+ // If an alias already exists,
+ // and the update action is set to do nothing,
+ // then gosh-darn it, do nothing.
+ return NULL;
+ }
+ }
+ }
+
+ // Replace any tokens in the pattern.
+ // Uses callback option to clean replacements. No sanitization.
+ // Pass empty BubbleableMetadata object to explicitly ignore cacheablity,
+ // as the result is never rendered.
+ $alias = $this->token->replace($pattern, $data, array(
+ 'clear' => TRUE,
+ 'callback' => array($this->aliasCleaner, 'cleanTokenValues'),
+ 'langcode' => $langcode,
+ 'pathauto' => TRUE,
+ ), new BubbleableMetadata());
+
+ // Check if the token replacement has not actually replaced any values. If
+ // that is the case, then stop because we should not generate an alias.
+ // @see token_scan()
+ $pattern_tokens_removed = preg_replace('/\[[^\s\]:]*:[^\s\]]*\]/', '', $pattern);
+ if ($alias === $pattern_tokens_removed) {
+ return NULL;
+ }
+
+ $alias = $this->aliasCleaner->cleanAlias($alias);
+
+ // Allow other modules to alter the alias.
+ $context['source'] = &$source;
+ $context['pattern'] = $pattern;
+ $this->moduleHandler->alter('pathauto_alias', $alias, $context);
+
+ // If we have arrived at an empty string, discontinue.
+ if (!Unicode::strlen($alias)) {
+ return NULL;
+ }
+
+ // If the alias already exists, generate a new, hopefully unique, variant.
+ $original_alias = $alias;
+ $this->aliasUniquifier->uniquify($alias, $source, $langcode);
+ if ($original_alias != $alias) {
+ // Alert the user why this happened.
+ $this->messenger->addMessage($this->t('The automatically generated alias %original_alias conflicted with an existing alias. Alias changed to %alias.', array(
+ '%original_alias' => $original_alias,
+ '%alias' => $alias,
+ )), $op);
+ }
+
+ // Return the generated alias if requested.
+ if ($op == 'return') {
+ return $alias;
+ }
+
+ // Build the new path alias array and send it off to be created.
+ $path = array(
+ 'source' => $source,
+ 'alias' => $alias,
+ 'language' => $langcode,
+ );
+
+ return $this->aliasStorageHelper->save($path, $existing_alias, $op);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPatternByEntity($entity_type_id, $bundle = '', $language = LanguageInterface::LANGCODE_NOT_SPECIFIED) {
+ $config = $this->configFactory->get('pathauto.pattern');
+
+ $pattern_id = "$entity_type_id:$bundle:$language";
+ if (!isset($this->patterns[$pattern_id])) {
+ $pattern = '';
+ $variables = array();
+ if ($language != LanguageInterface::LANGCODE_NOT_SPECIFIED) {
+ $variables[] = "{$entity_type_id}.bundles.{$bundle}.languages.{$language}";
+ }
+ if ($bundle) {
+ $variables[] = "{$entity_type_id}.bundles.{$bundle}.default";
+ }
+ $variables[] = "{$entity_type_id}.default";
+
+ foreach ($variables as $variable) {
+ if ($pattern = trim($config->get('patterns.' . $variable))) {
+ break;
+ }
+ }
+
+ $this->patterns[$pattern_id] = $pattern;
+ }
+
+ return $this->patterns[$pattern_id];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function resetCaches() {
+ $this->patterns = array();
+ $this->aliasCleaner->resetCaches();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateAlias(EntityInterface $entity, $op, array $options = array()) {
+ // Skip if the entity does not have the path field.
+ if (!($entity instanceof ContentEntityInterface) || !$entity->hasField('path')) {
+ return NULL;
+ }
+
+ // Skip if pathauto processing is disabled.
+ if (isset($entity->path->pathauto) && empty($entity->path->pathauto) && empty($options['force'])) {
+ return NULL;
+ }
+
+ $options += array('language' => $entity->language()->getId());
+ $type = $entity->getEntityTypeId();
+ $bundle = $entity->bundle();
+
+ // Skip processing if the entity has no pattern.
+ if (!$this->getPatternByEntity($type, $bundle, $options['language'])) {
+ return NULL;
+ }
+
+ // Deal with taxonomy specific logic.
+ if ($type == 'taxonomy_term') {
+
+ $config_forum = $this->configFactory->get('forum.settings');
+ if ($entity->getVocabularyId() == $config_forum->get('vocabulary')) {
+ $type = 'forum';
+ }
+ }
+
+ $result = $this->createAlias(
+ $type, $op, '/' . $entity->urlInfo()->getInternalPath(), array($type => $entity), $bundle, $options['language']);
+
+ if ($type == 'taxonomy_term' && empty($options['is_child'])) {
+ // For all children generate new aliases.
+ $options['is_child'] = TRUE;
+ unset($options['language']);
+ foreach ($this->getTermTree($entity->getVocabularyId(), $entity->id(), NULL, TRUE) as $subterm) {
+ $this->updateAlias($subterm, $op, $options);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Create a hierarchical representation of a vocabulary.
+ *
+ * @param int $vid
+ * The vocabulary ID to generate the tree for.
+ * @param int $parent
+ * The term ID under which to generate the tree. If 0, generate the tree
+ * for the entire vocabulary.
+ * @param int $max_depth
+ * The number of levels of the tree to return. Leave NULL to return all levels.
+ * @param bool $load_entities
+ * If TRUE, a full entity load will occur on the term objects. Otherwise they
+ * are partial objects queried directly from the {taxonomy_term_field_data}
+ * table to save execution time and memory consumption when listing large
+ * numbers of terms. Defaults to FALSE.
+ *
+ * @return array
+ * An array of all term objects in the tree. Each term object is extended
+ * to have "depth" and "parents" attributes in addition to its normal ones.
+ * Results are statically cached. Term objects will be partial or complete
+ * depending on the $load_entities parameter.
+ */
+ protected function getTermTree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE) {
+ return \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vid, $parent, $max_depth, $load_entities);
+ }
+
+}
diff --git a/src/PathautoManagerInterface.php b/src/PathautoManagerInterface.php
new file mode 100644
index 0000000..f806b8a
--- /dev/null
+++ b/src/PathautoManagerInterface.php
@@ -0,0 +1,111 @@
+type).
+ * @param string $langcode
+ * A string specify the path's language.
+ *
+ * @return array|string
+ * The alias that was created.
+ *
+ * @see _pathauto_set_alias()
+ */
+ public function createAlias($module, $op, $source, $data, $type = NULL, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED);
+
+ /**
+ * Creates or updates an alias for the given entity.
+ *
+ * @param EntityInterface $entity
+ * Entity for which to update the alias.
+ * @param string $op
+ * The operation performed (insert, update)
+ * @param array $options
+ * - force: will force updating the path
+ * - language: the language for which to create the alias
+ *
+ * @return array|null
+ * - An array with alias data in case the alias has been created or updated.
+ * - NULL if no operation performed.
+ */
+ public function updateAlias(EntityInterface $entity, $op, array $options = array());
+
+}
diff --git a/src/PathautoWidget.php b/src/PathautoWidget.php
new file mode 100644
index 0000000..4b36668
--- /dev/null
+++ b/src/PathautoWidget.php
@@ -0,0 +1,96 @@
+getEntity();
+
+ // Taxonomy terms do not have an actual fieldset for path settings.
+ // Merge in the defaults.
+ // @todo Impossible to do this in widget, use another solution
+ /*
+ $form['path'] += array(
+ '#type' => 'fieldset',
+ '#title' => $this->t('URL path settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => empty($form['path']['alias']),
+ '#group' => 'additional_settings',
+ '#attributes' => array(
+ 'class' => array('path-form'),
+ ),
+ '#access' => \Drupal::currentUser()->hasPermission('create url aliases') || \Drupal::currentUser()->hasPermission('administer url aliases'),
+ '#weight' => 30,
+ '#tree' => TRUE,
+ '#element_validate' => array('path_form_element_validate'),
+ );*/
+
+
+
+ $pattern = \Drupal::service('pathauto.manager')->getPatternByEntity($entity->getEntityTypeId(), $entity->bundle(), $entity->language()->getId());
+ if (empty($pattern)) {
+ return $element;
+ }
+
+
+ if (!isset($entity->path->pathauto)) {
+ if (!$entity->isNew()) {
+ module_load_include('inc', 'pathauto');
+ $path = \Drupal::service('path.alias_manager')->getAliasByPath('/' . $entity->urlInfo()->getInternalPath(), $entity->language()->getId());
+ $pathauto_alias = \Drupal::service('pathauto.manager')->createAlias($entity->getEntityTypeId(), 'return', '/' . $entity->urlInfo()->getInternalPath(), array($entity->getEntityType()->id() => $entity), $entity->bundle(), $entity->language()->getId());
+ $entity->path->pathauto = ($path != '/' . $entity->urlInfo()->getInternalPath() && $path == $pathauto_alias);
+ }
+ else {
+ $entity->path->pathauto = TRUE;
+ }
+ }
+ $element['pathauto'] = array(
+ '#type' => 'checkbox',
+ '#title' => $this->t('Generate automatic URL alias'),
+ '#default_value' => $entity->path->pathauto,
+ '#description' => $this->t('Uncheck this to create a custom alias below. Configure URL alias patterns.', array('@admin_link' => \Drupal::url('pathauto.patterns.form'))),
+ '#weight' => -1,
+ );
+
+ // Add JavaScript that will disable the path textfield when the automatic
+ // alias checkbox is checked.
+ $element['alias']['#states']['!enabled']['input[name="path[pathauto]"]'] = array('checked' => TRUE);
+
+
+ // Override path.module's vertical tabs summary.
+ $element['alias']['#attached']['library'] = ['pathauto/widget'];
+
+ if ($entity->path->pathauto && !empty($entity->old_alias) && empty($entity->path->alias)) {
+ $element['alias']['#default_value'] = $entity->old_alias;
+ $entity->path->alias = $entity->old_alias;
+ }
+
+
+ // For Pathauto to remember the old alias and prevent the Path module from
+ // deleting it when Pathauto wants to preserve it.
+ if (!empty($entity->path->alias)) {
+ $element['old_alias'] = array(
+ '#type' => 'value',
+ '#value' => $entity->path->alias,
+ );
+ }
+
+ return $element;
+ }
+}
diff --git a/src/Plugin/Action/UpdateAction.php b/src/Plugin/Action/UpdateAction.php
new file mode 100644
index 0000000..fd7b102
--- /dev/null
+++ b/src/Plugin/Action/UpdateAction.php
@@ -0,0 +1,39 @@
+path = new \stdClass();
+ $entity->path->pathauto = TRUE;
+ \Drupal::service('pathauto.manager')->updateAlias($entity, 'bulkupdate', array('message' => TRUE));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ $result = AccessResult::allowedIfHasPermission($account, 'create url aliases');
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+}
diff --git a/src/Plugin/pathauto/AliasType/EntityAliasTypeBase.php b/src/Plugin/pathauto/AliasType/EntityAliasTypeBase.php
new file mode 100644
index 0000000..733b754
--- /dev/null
+++ b/src/Plugin/pathauto/AliasType/EntityAliasTypeBase.php
@@ -0,0 +1,322 @@
+moduleHandler = $module_handler;
+ $this->languageManager = $language_manager;
+ $this->entityManager = $entity_manager;
+ $this->setConfiguration($configuration);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('module_handler'),
+ $container->get('language_manager'),
+ $container->get('entity.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConfiguration() {
+ return $this->configuration;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setConfiguration(array $configuration) {
+ $this->configuration = NestedArray::mergeDeep(
+ $this->defaultConfiguration(),
+ $configuration
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ return array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLabel() {
+ $definition = $this->getPluginDefinition();
+ // Cast the admin label to a string since it is an object.
+ // @see \Drupal\Core\StringTranslation\TranslationWrapper
+ return (string) $definition['label'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTokenTypes() {
+ $definition = $this->getPluginDefinition();
+ return $definition['types'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $form = array(
+ '#type' => 'details',
+ '#title' => $this->getLabel(),
+ '#open' => TRUE,
+ '#tree' => TRUE,
+ );
+
+ // Prompt for the default pattern for this module.
+ $key = 'default';
+
+ $form[$key] = array(
+ '#type' => 'textfield',
+ '#title' => $this->getPatternDescription(),
+ '#default_value' => $this->configuration['default'],
+ '#size' => 65,
+ '#maxlength' => 1280,
+ '#element_validate' => array('token_element_validate'),
+ '#after_build' => array('token_element_validate'),
+ '#token_types' => $this->getTokenTypes(),
+ '#min_tokens' => 1,
+ );
+
+ // If the module supports a set of specialized patterns, set
+ // them up here.
+ $patterns = $this->getPatterns();
+ foreach ($patterns as $itemname => $itemlabel) {
+ $key = 'default';
+ $form['bundles'][$itemname][$key] = array(
+ '#type' => 'textfield',
+ '#title' => $itemlabel,
+ '#default_value' => isset($this->configuration['bundles'][$itemname][$key]) ? $this->configuration['bundles'][$itemname][$key] : NULL,
+ '#size' => 65,
+ '#maxlength' => 1280,
+ '#element_validate' => array('token_element_validate'),
+ '#after_build' => array('token_element_validate'),
+ '#token_types' => $this->getTokenTypes(),
+ '#min_tokens' => 1,
+ );
+ }
+
+ // Show the token help relevant to this pattern type.
+ $form['token_help'] = array(
+ '#theme' => 'token_tree',
+ '#token_types' => $this->getTokenTypes(),
+ '#dialog' => TRUE,
+ );
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPatterns() {
+ $patterns = [];
+ $languages = $this->languageManager->getLanguages();
+ if ($this->entityManager->getDefinition($this->getEntityTypeId())->hasKey('bundle')) {
+ foreach ($this->getBundles() as $bundle => $bundle_label) {
+ if (count($languages) && $this->isContentTranslationEnabled($bundle)) {
+ $patterns[$bundle] = $this->t('Default path pattern for @bundle (applies to all @bundle fields with blank patterns below)', array('@bundle' => $bundle_label));
+ foreach ($languages as $language) {
+ $patterns[$bundle . '_' . $language->getId()] = $this->t('Pattern for all @language @bundle paths', array(
+ '@bundle' => $bundle_label,
+ '@language' => $language->getName()
+ ));
+ }
+ }
+ else {
+ $patterns[$bundle] = $this->t('Pattern for all @bundle paths', array('@bundle' => $bundle_label));
+ }
+ }
+ }
+ return $patterns;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function batchUpdate(&$context) {
+ if (!isset($context['sandbox']['current'])) {
+ $context['sandbox']['count'] = 0;
+ $context['sandbox']['current'] = 0;
+ }
+
+ $entity_type = $this->entityManager->getDefinition($this->getEntityTypeId());
+ $id_key = $entity_type->getKey('id');
+
+ $query = db_select($entity_type->get('base_table'), 'base_table');
+ $query->leftJoin('url_alias', 'ua', "CONCAT('" . $this->getSourcePrefix() . "' , base_table.$id_key) = ua.source");
+ $query->addField('base_table', $id_key, 'id');
+ $query->isNull('ua.source');
+ $query->condition('base_table.' . $id_key, $context['sandbox']['current'], '>');
+ $query->orderBy('base_table.' . $id_key);
+ $query->addTag('pathauto_bulk_update');
+ $query->addMetaData('entity', $this->getEntityTypeId());
+
+ // Get the total amount of items to process.
+ if (!isset($context['sandbox']['total'])) {
+ $context['sandbox']['total'] = $query->countQuery()->execute()->fetchField();
+
+ // If there are no nodes to update, the stop immediately.
+ if (!$context['sandbox']['total']) {
+ $context['finished'] = 1;
+ return;
+ }
+ }
+
+ $query->range(0, 25);
+ $ids = $query->execute()->fetchCol();
+
+ $this->bulkUpdate($ids);
+ $context['sandbox']['count'] += count($ids);
+ $context['sandbox']['current'] = max($ids);
+ $context['message'] = t('Updated alias for %label @id.', array('%label' => $entity_type->getLabel(), '@id' => end($ids)));
+
+ if ($context['sandbox']['count'] != $context['sandbox']['total']) {
+ $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total'];
+ }
+ }
+
+ /**
+ * Returns the entity type ID.
+ *
+ * @return string
+ * The entity type ID.
+ */
+ protected function getEntityTypeId() {
+ return $this->getPluginId();
+ }
+
+ /**
+ * Update the URL aliases for multiple entities.
+ *
+ * @param array $ids
+ * An array of entity IDs IDs.
+ * @param array $options
+ * An optional array of additional options.
+ */
+ protected function bulkUpdate(array $ids, array $options = array()) {
+ $options += array('message' => FALSE);
+
+ $entities = $this->entityManager->getStorage($this->getEntityTypeId())->loadMultiple($ids);
+ foreach ($entities as $node) {
+ \Drupal::service('pathauto.manager')->updateAlias($node, 'bulkupdate', $options);
+ }
+
+ if (!empty($options['message'])) {
+ drupal_set_message(\Drupal::translation()->formatPlural(count($ids), 'Updated URL alias for 1 node.', 'Updated URL aliases for @count nodes.'));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function calculateDependencies() {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ }
+
+ /**
+ * Returns bundles.
+ *
+ * @return string[]
+ * An array of bundle labels, keyed by bundle.
+ */
+ protected function getBundles() {
+ return array_map(function ($bundle_info) {
+ return $bundle_info['label'];
+ }, $this->entityManager->getBundleInfo($this->getEntityTypeId()));
+ }
+
+ /**
+ * Checks if a bundle is enabled for translation.
+ *
+ * @param string $bundle
+ * The bundle.
+ *
+ * @return bool
+ * TRUE if content translation is enabled for the bundle.
+ */
+ protected function isContentTranslationEnabled($bundle) {
+ return $this->moduleHandler->moduleExists('content_translation') && \Drupal::service('content_translation.manager')->isEnabled($this->getEntityTypeId(), $bundle);
+ }
+
+}
diff --git a/src/Plugin/pathauto/AliasType/ForumAliasType.php b/src/Plugin/pathauto/AliasType/ForumAliasType.php
new file mode 100644
index 0000000..8b88921
--- /dev/null
+++ b/src/Plugin/pathauto/AliasType/ForumAliasType.php
@@ -0,0 +1,60 @@
+t('Pattern for forums and forum containers');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPatterns() {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ return array('default' => array('/[term:vocabulary]/[term:name]')) + parent::defaultConfiguration();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getEntityTypeId() {
+ return 'taxonomy_term';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSourcePrefix() {
+ return 'forum/';
+ }
+
+}
diff --git a/src/Plugin/pathauto/AliasType/NodeAliasType.php b/src/Plugin/pathauto/AliasType/NodeAliasType.php
new file mode 100644
index 0000000..28859ea
--- /dev/null
+++ b/src/Plugin/pathauto/AliasType/NodeAliasType.php
@@ -0,0 +1,46 @@
+t('Default path pattern (applies to all content types with blank patterns below)');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ return array('default' => array('/content/[node:title]')) + parent::defaultConfiguration();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSourcePrefix() {
+ return 'node/';
+ }
+
+}
diff --git a/src/Plugin/pathauto/AliasType/TaxonomyTermAliasType.php b/src/Plugin/pathauto/AliasType/TaxonomyTermAliasType.php
new file mode 100644
index 0000000..448d2e4
--- /dev/null
+++ b/src/Plugin/pathauto/AliasType/TaxonomyTermAliasType.php
@@ -0,0 +1,46 @@
+t('Default path pattern (applies to all vocabularies with blank patterns below)');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ return array('default' => array('/[term:vocabulary]/[term:name]')) + parent::defaultConfiguration();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSourcePrefix() {
+ return 'taxonomy/term/';
+ }
+
+}
diff --git a/src/Plugin/pathauto/AliasType/UserAliasType.php b/src/Plugin/pathauto/AliasType/UserAliasType.php
new file mode 100644
index 0000000..616d594
--- /dev/null
+++ b/src/Plugin/pathauto/AliasType/UserAliasType.php
@@ -0,0 +1,46 @@
+t('Pattern for user account page paths');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ return array('default' => array('/users/[user:name]')) + parent::defaultConfiguration();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSourcePrefix() {
+ return '/user/';
+ }
+
+}
diff --git a/src/Tests/AliasType/NodeAliasTest.php b/src/Tests/AliasType/NodeAliasTest.php
new file mode 100644
index 0000000..c5bfede
--- /dev/null
+++ b/src/Tests/AliasType/NodeAliasTest.php
@@ -0,0 +1,53 @@
+container->get('plugin.manager.alias_type');
+
+ /** @var \Drupal\pathauto\AliasTypeInterface $node_type */
+ $node_type = $manager->createInstance('node');
+
+ $patterns = $node_type->getPatterns();
+ $this->assertTrue((array_key_exists('node', $patterns)), "Node pattern exists.");
+ $this->assertEqual($patterns['node'], 'Pattern for all Content paths', "Node pattern description matches.");
+
+ $token_types = $node_type->getTokenTypes();
+ $this->assertTrue(in_array('node', $token_types), "Node token type exists.");
+
+ $label = $node_type->getLabel();
+ $this->assertEqual($label, 'Content', "Plugin label matches.");
+
+ $default_config = $node_type->defaultConfiguration();
+
+ $this->assertTrue(array_key_exists('default', $default_config), "Default key exists.");
+ $this->assertEqual($default_config['default'][0], '/content/[node:title]', "Default content pattern matches.");
+
+ }
+
+}
diff --git a/src/Tests/PathautoBulkUpdateTest.php b/src/Tests/PathautoBulkUpdateTest.php
new file mode 100644
index 0000000..f9f03ec
--- /dev/null
+++ b/src/Tests/PathautoBulkUpdateTest.php
@@ -0,0 +1,93 @@
+adminUser = $this->drupalCreateUser($permissions);
+ $this->drupalLogin($this->adminUser);
+ }
+
+
+ function testBulkUpdate() {
+ // Create some nodes.
+ $this->nodes = array();
+ for ($i = 1; $i <= 5; $i++) {
+ $node = $this->drupalCreateNode();
+ $this->nodes[$node->id()] = $node;
+ }
+
+ // Clear out all aliases.
+ $this->deleteAllAliases();
+
+ // Bulk create aliases.
+ $edit = array(
+ 'update[node]' => TRUE,
+ 'update[user]' => TRUE,
+ );
+ $this->drupalPostForm('admin/config/search/path/update_bulk', $edit, t('Update'));
+ $this->assertText('Generated 7 URL aliases.'); // 5 nodes + 2 users
+
+ // Check that aliases have actually been created.
+ foreach ($this->nodes as $node) {
+ $this->assertEntityAliasExists($node);
+ }
+ $this->assertEntityAliasExists($this->adminUser);
+
+ // Add a new node.
+ $new_node = $this->drupalCreateNode(array('path' => array('alias' => '', 'pathauto' => FALSE)));
+
+ // Run the update again which should only run against the new node.
+ $this->drupalPostForm('admin/config/search/path/update_bulk', $edit, t('Update'));
+ $this->assertText('Generated 1 URL alias.'); // 1 node + 0 users
+
+ $this->assertEntityAliasExists($new_node);
+ }
+}
diff --git a/src/Tests/PathautoLocaleTest.php b/src/Tests/PathautoLocaleTest.php
new file mode 100644
index 0000000..1a1d4f9
--- /dev/null
+++ b/src/Tests/PathautoLocaleTest.php
@@ -0,0 +1,85 @@
+save();
+
+ $node = array(
+ 'title' => 'English node',
+ 'langcode' => 'en',
+ 'path' => array(array(
+ 'alias' => '/english-node',
+ 'pathauto' => FALSE,
+ )),
+ );
+ $node = $this->drupalCreateNode($node);
+ $english_alias = \Drupal::service('path.alias_storage')->load(array('alias' => '/english-node', 'langcode' => 'en'));
+ $this->assertTrue($english_alias, 'Alias created with proper language.');
+
+ // Also save a French alias that should not be left alone, even though
+ // it is the newer alias.
+ $this->saveEntityAlias($node, '/french-node', 'fr');
+
+ // Add an alias with the soon-to-be generated alias, causing the upcoming
+ // alias update to generate a unique alias with the '-0' suffix.
+ $this->saveAlias('/node/invalid', '/content/english-node', Language::LANGCODE_NOT_SPECIFIED);
+
+ // Update the node, triggering a change in the English alias.
+ $node->path->pathauto = TRUE;
+ $node->save();
+
+ // Check that the new English alias replaced the old one.
+ $this->assertEntityAlias($node, '/content/english-node-0', 'en');
+ $this->assertEntityAlias($node, '/french-node', 'fr');
+ $this->assertAliasExists(array('pid' => $english_alias['pid'], 'alias' => '/content/english-node-0'));
+
+ // Create a new node with the same title as before but without
+ // specifying a language.
+ $node = $this->drupalCreateNode(array('title' => 'English node', 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED));
+
+ // Check that the new node had a unique alias generated with the '-1'
+ // suffix.
+ $this->assertEntityAlias($node, '/content/english-node-1', LanguageInterface::LANGCODE_NOT_SPECIFIED);
+ }
+}
+
diff --git a/src/Tests/PathautoNodeWebTest.php b/src/Tests/PathautoNodeWebTest.php
new file mode 100644
index 0000000..eb68e9e
--- /dev/null
+++ b/src/Tests/PathautoNodeWebTest.php
@@ -0,0 +1,165 @@
+drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
+ $this->drupalCreateContentType(array('type' => 'article'));
+
+ // Allow other modules to add additional permissions for the admin user.
+ $permissions = array(
+ 'administer pathauto',
+ 'administer url aliases',
+ 'create url aliases',
+ 'administer nodes',
+ 'bypass node access',
+ 'access content overview',
+ );
+ $this->adminUser = $this->drupalCreateUser($permissions);
+ $this->drupalLogin($this->adminUser);
+ }
+
+ /**
+ * Tests editing nodes with different settings.
+ */
+ function testNodeEditing() {
+ // Ensure that the Pathauto checkbox is checked by default on the node add form.
+ $this->drupalGet('node/add/page');
+ $this->assertFieldChecked('edit-path-0-pathauto');
+
+ // Create a node by saving the node form.
+ $title = ' Testing: node title [';
+ $automatic_alias = '/content/testing-node-title';
+ $this->drupalPostForm(NULL, array('title[0][value]' => $title), t('Save and publish'));
+ $node = $this->drupalGetNodeByTitle($title);
+
+ // Look for alias generated in the form.
+ $this->drupalGet("node/{$node->id()}/edit");
+ $this->assertFieldChecked('edit-path-0-pathauto');
+ $this->assertFieldByName('path[0][alias]', $automatic_alias, 'Generated alias visible in the path alias field.');
+
+ // Check whether the alias actually works.
+ $this->drupalGet($automatic_alias);
+ $this->assertText($title, 'Node accessible through automatic alias.');
+
+ // Manually set the node's alias.
+ $manual_alias = '/content/' . $node->id();
+ $edit = array(
+ 'path[0][pathauto]' => FALSE,
+ 'path[0][alias]' => $manual_alias,
+ );
+ $this->drupalPostForm($node->urlInfo('edit-form'), $edit, t('Save and keep published'));
+ $this->assertRaw(t('@type %title has been updated.', array('@type' => 'page', '%title' => $title)));
+
+ // Check that the automatic alias checkbox is now unchecked by default.
+ $this->drupalGet("node/{$node->id()}/edit");
+ $this->assertNoFieldChecked('edit-path-0-pathauto');
+ $this->assertFieldByName('path[0][alias]', $manual_alias);
+
+ // Submit the node form with the default values.
+ $this->drupalPostForm(NULL, array('path[0][pathauto]' => FALSE), t('Save and keep published'));
+ $this->assertRaw(t('@type %title has been updated.', array('@type' => 'page', '%title' => $title)));
+
+ // Test that the old (automatic) alias has been deleted and only accessible
+ // through the new (manual) alias.
+ $this->drupalGet($automatic_alias);
+ $this->assertResponse(404, 'Node not accessible through automatic alias.');
+ $this->drupalGet($manual_alias);
+ $this->assertText($title, 'Node accessible through manual alias.');
+
+ // Test that the manual alias is not kept for new nodes when the pathauto
+ // checkbox is ticked.
+ $title = 'Automatic Title';
+ $edit = array(
+ 'title[0][value]' => $title,
+ 'path[0][pathauto]' => TRUE,
+ 'path[0][alias]' => '/should-not-get-created',
+ );
+ $this->drupalPostForm('node/add/page', $edit, t('Save and publish'));
+ $this->assertNoAliasExists(array('alias' => 'should-not-get-created'));
+ $node = $this->drupalGetNodeByTitle($title);
+ $this->assertEntityAlias($node, '/content/automatic-title');
+
+ // Remove the pattern for nodes, the pathauto checkbox should not be
+ // displayed.
+ $config = $this->config('pathauto.pattern');
+ $config->set('patterns.node.default', '');
+ $config->save();
+ \Drupal::service('pathauto.manager')->resetCaches();
+
+ $this->drupalGet('node/add/article');
+ $this->assertNoFieldById('edit-path-0-pathauto');
+ $this->assertFieldByName('path[0][alias]', '');
+
+ $edit = array();
+ $edit['title'] = 'My test article';
+ $this->drupalCreateNode($edit);
+ //$this->drupalPostForm(NULL, $edit, t('Save and keep published'));
+ $node = $this->drupalGetNodeByTitle($edit['title']);
+
+ // Pathauto checkbox should still not exist.
+ $this->drupalGet($node->urlInfo('edit-form'));
+ $this->assertNoFieldById('edit-path-0-pathauto');
+ $this->assertFieldByName('path[0][alias]', '');
+ $this->assertNoEntityAlias($node);
+ }
+
+ /**
+ * Test node operations.
+ */
+ function testNodeOperations() {
+ $node1 = $this->drupalCreateNode(array('title' => 'node1'));
+ $node2 = $this->drupalCreateNode(array('title' => 'node2'));
+
+ // Delete all current URL aliases.
+ $this->deleteAllAliases();
+
+ $edit = array(
+ 'action' => 'pathauto_update_alias_node',
+ // @todo - here we expect the $node1 to be at 0 position, any better way?
+ 'node_bulk_form[0]' => TRUE,
+ );
+ $this->drupalPostForm('admin/content', $edit, t('Apply'));
+ $this->assertRaw(\Drupal::translation()->formatPlural(1, '%action was applied to @count item.', '%action was applied to @count items.', array(
+ '%action' => 'Update URL-Alias',
+ )));
+
+ $this->assertEntityAlias($node1, '/content/' . $node1->getTitle());
+ $this->assertEntityAlias($node2, '/node/' . $node2->id());
+ }
+
+}
diff --git a/src/Tests/PathautoTaxonomyWebTest.php b/src/Tests/PathautoTaxonomyWebTest.php
new file mode 100644
index 0000000..c1640c1
--- /dev/null
+++ b/src/Tests/PathautoTaxonomyWebTest.php
@@ -0,0 +1,106 @@
+adminUser = $this->drupalCreateUser($permissions);
+ $this->drupalLogin($this->adminUser);
+ }
+
+
+ /**
+ * Basic functional testing of Pathauto with taxonomy terms.
+ */
+ function testTermEditing() {
+ $this->drupalGet('admin/structure');
+ $this->drupalGet('admin/structure/taxonomy');
+
+ // Add vocabulary "tags".
+ $vocabulary = $this->addVocabulary(array('name' => 'tags', 'vid' => 'tags'));
+
+ // Create term for testing.
+ $name = 'Testing: term name [';
+ $automatic_alias = '/tags/testing-term-name';
+ $this->drupalPostForm('admin/structure/taxonomy/manage/tags/add', array('name[0][value]' => $name), 'Save');
+ $name = trim($name);
+ $this->assertText("Created new term $name.");
+ $term = $this->drupalGetTermByName($name);
+
+ // Look for alias generated in the form.
+ $this->drupalGet("taxonomy/term/{$term->id()}/edit");
+ $this->assertFieldChecked('edit-path-0-pathauto');
+ $this->assertFieldByName('path[0][alias]', $automatic_alias, 'Generated alias visible in the path alias field.');
+
+ // Check whether the alias actually works.
+ $this->drupalGet($automatic_alias);
+ $this->assertText($name, 'Term accessible through automatic alias.');
+
+ // Manually set the term's alias.
+ $manual_alias = '/tags/' . $term->id();
+ $edit = array(
+ 'path[0][pathauto]' => FALSE,
+ 'path[0][alias]' => $manual_alias,
+ );
+ $this->drupalPostForm("taxonomy/term/{$term->id()}/edit", $edit, t('Save'));
+ $this->assertText("Updated term $name.");
+
+ // Check that the automatic alias checkbox is now unchecked by default.
+ $this->drupalGet("taxonomy/term/{$term->id()}/edit");
+ $this->assertNoFieldChecked('edit-path-0-pathauto');
+ $this->assertFieldByName('path[0][alias]', $manual_alias);
+
+ // Submit the term form with the default values.
+ $this->drupalPostForm(NULL, array('path[0][pathauto]' => FALSE), t('Save'));
+ $this->assertText("Updated term $name.");
+
+ // Test that the old (automatic) alias has been deleted and only accessible
+ // through the new (manual) alias.
+ $this->drupalGet($automatic_alias);
+ $this->assertResponse(404, 'Term not accessible through automatic alias.');
+ $this->drupalGet($manual_alias);
+ $this->assertText($name, 'Term accessible through manual alias.');
+ }
+
+
+}
diff --git a/src/Tests/PathautoTestHelperTrait.php b/src/Tests/PathautoTestHelperTrait.php
new file mode 100644
index 0000000..4dec728
--- /dev/null
+++ b/src/Tests/PathautoTestHelperTrait.php
@@ -0,0 +1,133 @@
+generate($type, array($token => $token), array($type => $object), [], $bubbleable_metadata);
+ $tokens += array($token => '');
+ $this->assertIdentical($tokens[$token], $expected, t("Token value for [@type:@token] was '@actual', expected value '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $tokens[$token], '@expected' => $expected)));
+ }
+
+ public function saveAlias($source, $alias, $langcode = Language::LANGCODE_NOT_SPECIFIED) {
+ \Drupal::service('path.alias_storage')->delete(array('source' => $source, 'language', 'langcode' => $langcode));
+ return \Drupal::service('path.alias_storage')->save($source, $alias, $langcode);
+ }
+
+ public function saveEntityAlias(EntityInterface $entity, $alias, $langcode = NULL) {
+ // By default, use the entity language.
+ if (!$langcode) {
+ $langcode = $entity->language()->getId();
+ }
+ return $this->saveAlias('/' . $entity->urlInfo()->getInternalPath(), $alias, $langcode);
+ }
+
+ public function assertEntityAlias(EntityInterface $entity, $expected_alias, $langcode = NULL) {
+ // By default, use the entity language.
+ if (!$langcode) {
+ $langcode = $entity->language()->getId();
+ }
+ $this->assertAlias('/' . $entity->urlInfo()->getInternalPath(), $expected_alias, $langcode);
+ }
+
+ public function assertEntityAliasExists(EntityInterface $entity) {
+ return $this->assertAliasExists(array('source' => '/' . $entity->urlInfo()->getInternalPath()));
+ }
+
+ public function assertNoEntityAlias(EntityInterface $entity, $langcode = NULL) {
+ // By default, use the entity language.
+ if (!$langcode) {
+ $langcode = $entity->language()->getId();
+ }
+ $this->assertEntityAlias($entity, '/' . $entity->urlInfo()->getInternalPath(), $langcode);
+ }
+
+ public function assertNoEntityAliasExists(EntityInterface $entity) {
+ $this->assertNoAliasExists(array('source' => '/' . $entity->urlInfo()->getInternalPath()));
+ }
+
+ public function assertAlias($source, $expected_alias, $langcode = Language::LANGCODE_NOT_SPECIFIED) {
+ $alias = array('alias' => $source);
+ foreach (db_select('url_alias')->fields('url_alias')->condition('source', $source)->execute() as $row) {
+ $alias = (array) $row;
+ if ($row->alias == $expected_alias) {
+ break;
+ }
+ }
+ $this->assertIdentical($alias['alias'], $expected_alias, t("Alias for %source with language '@language' was %actual, expected %expected.",
+ array('%source' => $source, '%actual' => $alias['alias'], '%expected' => $expected_alias, '@language' => $langcode)));
+ }
+
+ public function assertAliasExists($conditions) {
+ $path = \Drupal::service('path.alias_storage')->load($conditions);
+ $this->assertTrue($path, t('Alias with conditions @conditions found.', array('@conditions' => var_export($conditions, TRUE))));
+ return $path;
+ }
+
+ public function assertNoAliasExists($conditions) {
+ $alias = \Drupal::service('path.alias_storage')->load($conditions);
+ $this->assertFalse($alias, t('Alias with conditions @conditions not found.', array('@conditions' => var_export($conditions, TRUE))));
+ }
+
+ public function deleteAllAliases() {
+ db_delete('url_alias')->execute();
+ \Drupal::service('path.alias_manager')->cacheClear();
+ }
+
+ /**
+ * @param array $values
+ * @return \Drupal\taxonomy\VocabularyInterface
+ */
+ public function addVocabulary(array $values = array()) {
+ $name = Unicode::strtolower($this->randomMachineName(5));
+ $values += array(
+ 'name' => $name,
+ 'vid' => $name,
+ );
+ $vocabulary = entity_create('taxonomy_vocabulary', $values);
+ $vocabulary->save();
+
+ return $vocabulary;
+ }
+
+ public function addTerm(VocabularyInterface $vocabulary, array $values = array()) {
+ $values += array(
+ 'name' => Unicode::strtolower($this->randomMachineName(5)),
+ 'vid' => $vocabulary->id(),
+ );
+
+ $term = entity_create('taxonomy_term', $values);
+ $term->save();
+ return $term;
+ }
+
+ public function assertEntityPattern($entity_type, $bundle, $langcode = Language::LANGCODE_NOT_SPECIFIED, $expected) {
+ \Drupal::service('pathauto.manager')->resetCaches();
+ $pattern = \Drupal::service('pathauto.manager')->getPatternByEntity($entity_type, $bundle, $langcode);
+ $this->assertIdentical($expected, $pattern);
+ }
+
+ public function drupalGetTermByName($name, $reset = FALSE) {
+ if ($reset) {
+ // @todo - implement cache reset.
+ }
+ $terms = \Drupal::entityManager()->getStorage('taxonomy_term')->loadByProperties(array('name' => $name));
+ return !empty($terms) ? reset($terms) : FALSE;
+ }
+}
diff --git a/src/Tests/PathautoTokenTest.php b/src/Tests/PathautoTokenTest.php
new file mode 100644
index 0000000..2ae50c9
--- /dev/null
+++ b/src/Tests/PathautoTokenTest.php
@@ -0,0 +1,82 @@
+installConfig(array('pathauto'));
+
+ $array = array(
+ 'test first arg',
+ 'The Array / value',
+ );
+
+ $tokens = array(
+ 'join-path' => 'test-first-arg/array-value',
+ );
+ $data['array'] = $array;
+ $replacements = $this->assertTokens('array', $data, $tokens);
+
+ // Ensure that the cleanTokenValues() method does not alter this token value.
+ /* @var \Drupal\pathauto\AliasCleanerInterface $alias_cleaner */
+ $alias_cleaner = \Drupal::service('pathauto.alias_cleaner');
+ $alias_cleaner->cleanTokenValues($replacements, $data, array());
+ $this->assertEqual($replacements['[array:join-path]'], 'test-first-arg/array-value');
+ }
+
+ /**
+ * Function copied from TokenTestHelper::assertTokens().
+ */
+ public function assertTokens($type, array $data, array $tokens, array $options = array()) {
+ $input = $this->mapTokenNames($type, array_keys($tokens));
+ $bubbleable_metadata = new BubbleableMetadata();
+ $replacements = \Drupal::token()->generate($type, $input, $data, $options, $bubbleable_metadata);
+ foreach ($tokens as $name => $expected) {
+ $token = $input[$name];
+ if (!isset($expected)) {
+ $this->assertTrue(!isset($values[$token]), t("Token value for @token was not generated.", array('@type' => $type, '@token' => $token)));
+ }
+ elseif (!isset($replacements[$token])) {
+ $this->fail(t("Token value for @token was not generated.", array('@type' => $type, '@token' => $token)));
+ }
+ elseif (!empty($options['regex'])) {
+ $this->assertTrue(preg_match('/^' . $expected . '$/', $replacements[$token]), t("Token value for @token was '@actual', matching regular expression pattern '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected)));
+ }
+ else {
+ $this->assertIdentical($replacements[$token], $expected, t("Token value for @token was '@actual', expected value '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected)));
+ }
+ }
+
+ return $replacements;
+ }
+
+ public function mapTokenNames($type, array $tokens = array()) {
+ $return = array();
+ foreach ($tokens as $token) {
+ $return[$token] = "[$type:$token]";
+ }
+ return $return;
+ }
+}
diff --git a/src/Tests/PathautoUiTest.php b/src/Tests/PathautoUiTest.php
new file mode 100644
index 0000000..1415aa9
--- /dev/null
+++ b/src/Tests/PathautoUiTest.php
@@ -0,0 +1,103 @@
+drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
+ $this->drupalCreateContentType(array('type' => 'article'));
+
+ // Allow other modules to add additional permissions for the admin user.
+ $permissions = array(
+ 'administer pathauto',
+ 'administer url aliases',
+ 'create url aliases',
+ 'administer nodes',
+ 'bypass node access',
+ 'access content overview',
+ );
+ $this->adminUser = $this->drupalCreateUser($permissions);
+ $this->drupalLogin($this->adminUser);
+ }
+
+ function testSettingsValidation() {
+ $edit = array();
+ $edit['max_length'] = 'abc';
+ $edit['max_component_length'] = 'abc';
+ $this->drupalPostForm('admin/config/search/path/settings', $edit, 'Save configuration');
+ /*$this->assertText('The field Maximum alias length is not a valid number.');
+ $this->assertText('The field Maximum component length is not a valid number.');*/
+ $this->assertNoText('The configuration options have been saved.');
+
+ $edit['max_length'] = '0';
+ $edit['max_component_length'] = '0';
+ $this->drupalPostForm('admin/config/search/path/settings', $edit, 'Save configuration');
+ /*$this->assertText('The field Maximum alias length cannot be less than 1.');
+ $this->assertText('The field Maximum component length cannot be less than 1.');*/
+ $this->assertNoText('The configuration options have been saved.');
+
+ $edit['max_length'] = '999';
+ $edit['max_component_length'] = '999';
+ $this->drupalPostForm('admin/config/search/path/settings', $edit, 'Save configuration');
+ /*$this->assertText('The field Maximum alias length cannot be greater than 255.');
+ $this->assertText('The field Maximum component length cannot be greater than 255.');*/
+ $this->assertNoText('The configuration options have been saved.');
+
+ $edit['max_length'] = '50';
+ $edit['max_component_length'] = '50';
+ $this->drupalPostForm('admin/config/search/path/settings', $edit, 'Save configuration');
+ $this->assertText('The configuration options have been saved.');
+ }
+
+ function testPatternsValidation() {
+ $edit = array();
+ $this->drupalGet('admin/config/search/path/patterns');
+ $edit['node[default]'] = '[node:title]/[user:name]/[term:name]';
+ $edit['node[bundles][page][default]'] = 'page';
+ $this->drupalPostForm('admin/config/search/path/patterns', $edit, 'Save configuration');
+ $this->assertText('The Default path pattern (applies to all content types with blank patterns below) is using the following invalid tokens: [user:name], [term:name].');
+ $this->assertText('The Pattern for all Basic page paths cannot contain fewer than one token.');
+ $this->assertNoText('The configuration options have been saved.');
+
+ $edit['node[default]'] = '[node:title]';
+ $edit['node[bundles][page][default]'] = 'page/[node:title]';
+ $edit['node[bundles][article][default]'] = '';
+ $this->drupalPostForm('admin/config/search/path/patterns', $edit, 'Save configuration');
+ $this->assertText('The configuration options have been saved.');
+ }
+
+}
diff --git a/src/Tests/PathautoUnitTest.php b/src/Tests/PathautoUnitTest.php
new file mode 100644
index 0000000..5d4497f
--- /dev/null
+++ b/src/Tests/PathautoUnitTest.php
@@ -0,0 +1,391 @@
+installConfig(array('pathauto', 'taxonomy', 'system', 'node'));
+
+ $this->installEntitySchema('user');
+ $this->installEntitySchema('node');
+ $this->installEntitySchema('taxonomy_term');
+
+ $this->installSchema('node', array('node_access'));
+ $this->installSchema('system', array('url_alias', 'sequences', 'router'));
+
+ $type = NodeType::create(['type' => 'page']);
+ $type->save();
+ node_add_body_field($type);
+
+ \Drupal::service('router.builder')->rebuild();
+
+ $this->currentUser = entity_create('user', array('name' => $this->randomMachineName()));
+ $this->currentUser->save();
+ }
+
+ /**
+ * Test _pathauto_get_schema_alias_maxlength().
+ */
+ public function testGetSchemaAliasMaxLength() {
+ $this->assertIdentical(\Drupal::service('pathauto.alias_storage_helper')->getAliasSchemaMaxlength(), 255);
+ }
+
+ /**
+ * Test pathauto_pattern_load_by_entity().
+ */
+ public function testPatternLoadByEntity() {
+ $this->config('pathauto.pattern')
+ ->set('patterns.node.bundles.article.default', '/article/[node:title]')
+ ->set('patterns.node.bundles.article.languages.en', '/article/en/[node:title]')
+ ->set('patterns.node.bundles.page.default', '/[node:title]')
+ ->save();
+
+ $tests = array(
+ array(
+ 'entity' => 'node',
+ 'bundle' => 'article',
+ 'language' => 'fr',
+ 'expected' => '/article/[node:title]',
+ ),
+ array(
+ 'entity' => 'node',
+ 'bundle' => 'article',
+ 'language' => 'en',
+ 'expected' => '/article/en/[node:title]',
+ ),
+ array(
+ 'entity' => 'node',
+ 'bundle' => 'article',
+ 'language' => Language::LANGCODE_NOT_SPECIFIED,
+ 'expected' => '/article/[node:title]',
+ ),
+ array(
+ 'entity' => 'node',
+ 'bundle' => 'page',
+ 'language' => 'en',
+ 'expected' => '/[node:title]',
+ ),
+ array(
+ 'entity' => 'user',
+ 'bundle' => 'user',
+ 'language' => Language::LANGCODE_NOT_SPECIFIED,
+ 'expected' => '/users/[user:name]',
+ ),
+ array(
+ 'entity' => 'invalid-entity',
+ 'bundle' => '',
+ 'language' => Language::LANGCODE_NOT_SPECIFIED,
+ 'expected' => '',
+ ),
+ );
+ foreach ($tests as $test) {
+ $actual = \Drupal::service('pathauto.manager')->getPatternByEntity($test['entity'], $test['bundle'], $test['language']);
+ $this->assertIdentical($actual, $test['expected'], t("pathauto_pattern_load_by_entity('@entity', '@bundle', '@language') returned '@actual', expected '@expected'", array(
+ '@entity' => $test['entity'],
+ '@bundle' => $test['bundle'],
+ '@language' => $test['language'],
+ '@actual' => $actual,
+ '@expected' => $test['expected'],
+ )));
+ }
+ }
+
+ /**
+ * Test pathauto_cleanstring().
+ */
+ public function testCleanString() {
+
+ $config = $this->config('pathauto.settings');
+
+ $tests = array();
+ $config->set('ignore_words', ', in, is,that, the , this, with, ');
+ $config->set('max_component_length', 35);
+ $config->set('transliterate', TRUE);
+ $config->save();
+ \Drupal::service('pathauto.manager')->resetCaches();
+
+ // Test the 'ignored words' removal.
+ $tests['this'] = 'this';
+ $tests['this with that'] = 'this-with-that';
+ $tests['this thing with that thing'] = 'thing-thing';
+
+ // Test length truncation and duplicate separator removal.
+ $tests[' - Pathauto is the greatest - module ever in Drupal hiarticle - '] = 'pathauto-greatest-module-ever';
+
+ // Test that HTML tags are removed.
+ $tests['This text has