diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..ac27ec3 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,38 @@ +# PHP CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-php/ for more details +# +version: 2 +jobs: + build: + docker: + # specify the version you desire here + - image: circleci/php:7.1-browsers + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/mysql:9.4 + + working_directory: ~/repo + + steps: + - checkout + + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "composer.json" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - run: composer install -n --prefer-dist + + - save_cache: + paths: + - ./vendor + key: v1-dependencies-{{ checksum "composer.json" }} + + # run tests! + - run: phpunit + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f11b75 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ diff --git a/.travis.yml b/.travis.yml index f4bc5d1..05646c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,61 +1,16 @@ language: php - -sudo: false - -addons: - apt: - packages: - - libxml2-utils - php: - - 7.1 - - 7.2 - - 7.3 - - master - -matrix: - allow_failures: - - php: master - fast_finish: true - -env: - matrix: - - DEPENDENCIES="high" - - DEPENDENCIES="low" - global: - - DEFAULT_COMPOSER_FLAGS="--no-interaction --no-ansi --no-progress --no-suggest" +- '7.1' -before_install: - - ./build/tools/composer clear-cache +addons: + sonarcloud: + organization: "simon-peacock-github" + token: + secure: "secure-string=666f24e3ccd30b982a2e1a78b4517210c2cf412b" install: - - if [[ "$DEPENDENCIES" = 'high' ]]; then travis_retry ./build/tools/composer update $DEFAULT_COMPOSER_FLAGS; fi - - if [[ "$DEPENDENCIES" = 'low' ]]; then travis_retry ./build/tools/composer update $DEFAULT_COMPOSER_FLAGS --prefer-lowest; fi - -before_script: - - echo 'zend.assertions=1' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - - echo 'assert.exception=On' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - +- composer create-project drupal-composer/drupal-project:8.x-dev drupal --stability dev --no-interaction +- mkdir -p drupal/web/modules/${PWD##*/} && cp -a ${PWD##*/}* tests src drupal/web/modules/${PWD##*/} script: - - ./phpunit --coverage-clover=coverage.xml - - ./phpunit --configuration ./build/travis-ci-fail.xml > /dev/null; if [ $? -eq 0 ]; then echo "SHOULD FAIL"; false; else echo "fail checked"; fi; - - xmllint --noout --schema phpunit.xsd phpunit.xml - - xmllint --noout --schema phpunit.xsd tests/_files/configuration.xml - - xmllint --noout --schema phpunit.xsd tests/_files/configuration_empty.xml - - xmllint --noout --schema phpunit.xsd tests/_files/configuration_xinclude.xml -xinclude - -after_success: - - bash <(curl -s https://codecov.io/bash) - -notifications: - email: false +- drupal/vendor/bin/phpunit -c drupal/web/core drupal/web/modules/${PWD##*/}/tests/ -jobs: - include: - - stage: Static Code Analysis - php: 7.2 - env: php-cs-fixer - install: - - phpenv config-rm xdebug.ini - script: - - ./build/tools/php-cs-fixer fix --dry-run -v --show-progress=dots --diff-format=udiff diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..a5f5d44 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,7 @@ +#!/usr/bin/env groovy +// Repository can be found at https://github.com/dennisinteractive/lightning-jenkins-build-scripts +// Required Library call +@Library('lightning-shared-libraries@master') _ + +// CAll Drupal unit test +drupalUnitTest() diff --git a/README.md b/README.md index 2fbbb84..7c506a2 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ # phpunit_example +[![Build Status](https://travis-ci.org/simon-peacock/phpunit_example.svg?branch=master)](https://travis-ci.org/simon-peacock/phpunit_example) diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..745fa89 --- /dev/null +++ b/composer.json @@ -0,0 +1,16 @@ +{ + "name": "drupal/phpunit_example", + "type": "drupal-module", + "description": "Allow a term to be configured to show the content on a referenced node rather than the default term view.", + "keywords": ["Drupal", "Term", "phpunit"], + "license": "GPL-2.0+", + "homepage": "https://www.drupal.org/project/term_node", + "minimum-stability": "dev", + "support": { + "issues": "https://www.drupal.org/project/issues/term_node", + "source": "http://cgit.drupalcode.org/term_node" + }, + "require": { + "phpunit/phpunit": "7.x" + } +} diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..6dd1723 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,13 @@ +sonar.projectKey=phpunit_example +sonar.projectName=phpunit example +sonar.links.homepage=https://github.com/simon-peacock/phpunit_example + +sonar.sources=src +sonar.exclusions=**drupal** +sonar.host.url=http://ec2-34-255-179-73.eu-west-1.compute.amazonaws.com:9000 +sonar.verbose=false +sonar.sourceEncoding=UTF-8 +sonar.pullrequest.provider=github + +sonar.dependencyCheck.reportPath=dependency-check-report.xml +sonar.dependencyCheck.htmlReportPath=dependency-check-report.html diff --git a/src/ContentPartnershipKickerBuilder.php b/src/ContentPartnershipKickerBuilder.php new file mode 100644 index 0000000..7a5eaa8 --- /dev/null +++ b/src/ContentPartnershipKickerBuilder.php @@ -0,0 +1,55 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public function build(KickerInterface $kicker, ContentEntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) { + // If it is tagged with a term from the Content Partnerships taxonomy, + // then that term is displayed. + if ($entity->hasField('field_sponsored')) { + if ($reference = $entity->get('field_sponsored')->first()) { + $term = $this->entityTypeManager->getStorage('taxonomy_term') + ->load($reference->getString()); + if ($term) { + // A Content Partnerships term, + // then use it as the kicker but with no path. + $kicker + ->setEntity($term) + ->setText($term->getName()) + ->setBuilt(); + } + } + } + } + +} diff --git a/src/DefaultKickerBuilder.php b/src/DefaultKickerBuilder.php new file mode 100644 index 0000000..a946ed9 --- /dev/null +++ b/src/DefaultKickerBuilder.php @@ -0,0 +1,156 @@ +entityTypeManager = $entity_type_manager; + $this->router = $router; + $this->pathProcessor = $path_processor; + $this->nodeResolver = $node_resolver; + } + + /** + * {@inheritdoc} + */ + public function build(KickerInterface $kicker, ContentEntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) { + // The term used for the kicker should follow the same logic + // that we use for the breadcrumb/paths. + // It is the last entity that is in the path that should be used. + // NB: Cannot use the breadcrumb as it always builds the current page. + $path_elements = explode('/', $entity->toUrl()->toString()); + while (count($path_elements) > 1) { + array_pop($path_elements); + // Build a request for the path. + $route_request = $this->getRequestForPath(implode('/', $path_elements)); + if ($route_request) { + $route_match = RouteMatch::createFromRequest($route_request); + foreach ($route_match->getParameters() as $entity) { + if ($entity instanceof EntityInterface) { + + // If the entity is a node, it may be a term node, + // in which case we want to use the term's label not the node's. + if ($entity->getEntityTypeId() == 'node' && $tid = $this->nodeResolver->getReferencedBy($entity->id())) { + $entity = $this->entityTypeManager->getStorage('taxonomy_term') + ->load($tid); + } + + $kicker->setEntity($entity) + ->setText($entity->label()) + ->setUrl($entity->toUrl()) + ->setBuilt(); + // Found the last entity, so do no more. + return; + } + } + } + } + } + + /** + * Matches a path in the router. + * + * Shameless copy of core/modules/system/src/PathBasedBreadcrumbBuilder.php. + * + * @param string $path + * The request path with a leading slash. + * + * @return \Symfony\Component\HttpFoundation\Request + * A populated request object or NULL if the path couldn't be matched. + */ + protected function getRequestForPath($path) { + $request = Request::create($path); + // Performance optimization: set a short accept header to reduce overhead in + // AcceptHeaderMatcher when matching the request. + $request->headers->set('Accept', 'text/html'); + // Find the system path by resolving aliases, language prefix, etc. + $processed = $this->pathProcessor->processInbound($path, $request); + if (empty($processed) || !empty($exclude[$processed])) { + // This resolves to the front page. + return NULL; + } + + // Attempt to match this path to provide a fully built request. + try { + $request->attributes->add($this->router->matchRequest($request)); + return $request; + } + catch (ParamNotConvertedException $e) { + return NULL; + } + catch (ResourceNotFoundException $e) { + return NULL; + } + catch (MethodNotAllowedException $e) { + return NULL; + } + catch (AccessDeniedHttpException $e) { + return NULL; + } + } + +} diff --git a/src/Kicker.php b/src/Kicker.php new file mode 100644 index 0000000..09f72c7 --- /dev/null +++ b/src/Kicker.php @@ -0,0 +1,187 @@ +built; + } + + /** + * {@inheritdoc} + */ + public function setBuilt($bool = TRUE) { + $this->built = $bool; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setUrl(Url $url) { + $this->url = $url; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getUrl() { + if ($this->url instanceof Url) { + return $this->url; + } + + return NULL; + } + + /** + * {@inheritdoc} + */ + public function getText() { + return $this->text; + } + + /** + * {@inheritdoc} + */ + public function getEntity() { + if ($this->entity instanceof EntityInterface) { + return $this->entity; + } + + return NULL; + } + + /** + * {@inheritdoc} + */ + public function setText($text) { + $this->text = $text; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setEntity(EntityInterface $entity) { + $this->entity = $entity; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + if ($entity = $this->getEntity()) { + // If a term changes, then the cache should be invalidated. + $this->addCacheTags($entity->getCacheTags()); + } + + return $this->cacheTags; + } + + /** + * {@inheritdoc} + */ + public function toRenderable() { + if (!$this->built()) { + // Nothing to render. + return []; + } + + if (empty($this->getText())) { + // Nothing to render. + return []; + } + + $build = []; + if ($this->getEntity()) { + $build = [ + '#cache' => [ + 'contexts' => $this->getCacheContexts(), + 'tags' => $this->getCacheTags(), + 'max-age' => $this->getCacheMaxAge(), + ], + ]; + } + + if (empty($this->getUrl())) { + // Text only. + $build += [ + '#markup' => $this->getText(), + ]; + } + else { + $build += [ + '#type' => 'link', + '#title' => $this->getText(), + '#url' => $this->getUrl(), + ]; + } + + return $build; + } + + /** + * {@inheritdoc} + */ + public function reset() { + $this->url = NULL; + $this->text = NULL; + $this->entity = NULL; + $this->built = FALSE; + + return $this; + } + +} diff --git a/src/KickerBuilderInterface.php b/src/KickerBuilderInterface.php new file mode 100644 index 0000000..893f6a9 --- /dev/null +++ b/src/KickerBuilderInterface.php @@ -0,0 +1,29 @@ +setKicker($kicker); + } + + /** + * {@inheritdoc} + */ + public function setKicker(KickerInterface $kicker) { + $this->kicker = $kicker; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function addBuilder(KickerBuilderInterface $builder, $priority) { + $this->builders[$priority][] = $builder; + // Force the builders to be re-sorted. + $this->sortedBuilders = NULL; + } + + /** + * {@inheritdoc} + */ + public function buildKicker(ContentEntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) { + // Ensure a fresh kicker to start with. + $this->kicker->reset(); + + // Call the build method of registered kicker builders, + // until one of them returns the kicker. + foreach ($this->getSortedBuilders() as $builder) { + $builder->build($this->kicker, $entity, $display, $view_mode); + if ($this->kicker->built()) { + return $this->kicker; + } + } + + // Return an unbuilt kicker. + return $this->kicker->reset(); + } + + /** + * Returns the sorted array of kicker builders. + * + * @return KickerBuilderInterface[] + * An array of kicker builder objects. + */ + protected function getSortedBuilders() { + if (!isset($this->sortedBuilders)) { + // Sort the builders according to priority. + krsort($this->builders); + // Merge nested builders from $this->builders into $this->sortedBuilders. + $this->sortedBuilders = []; + foreach ($this->builders as $builders) { + $this->sortedBuilders = array_merge($this->sortedBuilders, $builders); + } + } + return $this->sortedBuilders; + } + +} diff --git a/src/KickerManagerInterface.php b/src/KickerManagerInterface.php new file mode 100644 index 0000000..bccfe10 --- /dev/null +++ b/src/KickerManagerInterface.php @@ -0,0 +1,51 @@ +manager = $manager; + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('dennis_kicker.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function viewElements(ContentEntityInterface $entity) { + return $this->manager->buildKicker( + $entity, + $this->getEntityViewDisplay(), + $this->getViewMode()) + ->toRenderable(); + } + +}