diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bf02210 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,11 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + ci: + name: CI + uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1 diff --git a/.gitignore b/.gitignore index a4d83b1..52bee4f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ /.php_cs.cache +/public +/vendor +composer.lock +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8984142..0000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: php - -matrix: - include: - - php: 5.6 - env: DB=MYSQL PHPCS_TEST=1 PHPUNIT_TEST=1 - - php: 7.0 - env: DB=PGSQL PHPUNIT_TEST=1 - - php: 7.1 - env: DB=MYSQL PHPUNIT_COVERAGE_TEST=1 - -before_script: - - phpenv rehash - - phpenv config-rm xdebug.ini - - - composer validate - - composer require --prefer-dist --no-update silverstripe/recipe-cms:^1.1 - - if [[ $DB == PGSQL ]]; then composer require --prefer-dist --no-update silverstripe/postgresql:^2.0; fi - - composer update - -script: - - if [[ $PHPUNIT_TEST ]]; then vendor/bin/phpunit; fi - - if [[ $PHPUNIT_COVERAGE_TEST ]]; then phpdbg -qrr vendor/bin/phpunit --coverage-clover=coverage.xml; fi - - if [[ $PHPCS_TEST ]]; then vendor/bin/phpcs -s --standard=PSR2 --exclude=PSR1.Methods.CamelCapsMethodName,Generic.Files.LineLength src/ tests/ *.php; fi - -after_success: - - if [[ $PHPUNIT_COVERAGE_TEST ]]; then bash <(curl -s https://codecov.io/bash) -f coverage.xml; fi \ No newline at end of file diff --git a/README.md b/README.md index f9ef04d..36dbba1 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,14 @@ QuinnInteractive\Seo\Analysis\MetaDescAnalysis: meta_desc_too_long_threshold: 320 ``` +### Other Options + +Other options can be found in the `private static` variables in the following files. They can be overridden in YAML in the usual way. + +- PageHealthExtension.php +- PageSeoExtension.php +- SiteConfigSettingsExtension.php + ## Assumptions This module assumes that you make use of the default `Content` field provided by `\Page`. If a specific page does not then you can specify one or multiple fields that contain your content. @@ -182,4 +190,4 @@ public function updateCollateContentFields($content) { ## Version -1.1.4 +2.0.0 diff --git a/composer.json b/composer.json index 6ce273e..e085e00 100644 --- a/composer.json +++ b/composer.json @@ -1,51 +1,66 @@ { - "name": "quinninteractive/silverstripe-seo", - "description": "An all-in-one SEO module for SilverStripe", - "type": "silverstripe-vendormodule", - "keywords": ["silverstripe", "seo", "facebook", "twitter", "opengraph", "search", "optimization", "optimisation"], - "license": "BSD-3-Clause", - "authors": [ - { - "name": "Reece Alexander", - "homepage": "https://vulcandigital.co.nz", - "role": "author" + "name": "quinninteractive/silverstripe-seo", + "description": "An all-in-one SEO module for SilverStripe", + "type": "silverstripe-vendormodule", + "keywords": [ + "silverstripe", + "seo", + "facebook", + "twitter", + "opengraph", + "search", + "optimization", + "optimisation" + ], + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Reece Alexander", + "homepage": "https://vulcandigital.co.nz", + "role": "author" + }, + { + "name": "Fred Condo", + "homepage": "https://quinn.com", + "role": "maintainer" + }, + { + "name": "Phil Quinn", + "homepage": "https://quinn.com", + "role": "maintainer" + } + ], + "require": { + "php": "^8.1", + "jonom/silverstripe-text-target-length": "^2.0", + "kub-at/php-simple-html-dom-parser": "^1.9", + "silverstripe/cms": "^5", + "wilr/silverstripe-googlesitemaps": "^3.1" }, - { - "name": "Fred Condo", - "homepage": "https://quinn.com", - "role": "maintainer" + "require-dev": { + "phpunit/phpunit": "^9.5", + "rector/rector": "^1.2" }, - { - "name": "Phil Quinn", - "homepage": "https://quinn.com", - "role": "maintainer" - } - ], - "require": { - "silverstripe/cms": "^4.1", - "jonom/silverstripe-text-target-length": "^2", - "wilr/silverstripe-googlesitemaps": "^2.1", - "kub-at/php-simple-html-dom-parser": "^1.7", - "axllent/silverstripe-trailing-slash": "^2.1" - }, - "require-dev": { - "phpunit/phpunit": "^5.7", - "squizlabs/php_codesniffer": "3.*" - }, - "autoload": { - "psr-4": { - "QuinnInteractive\\Seo\\": "src/", - "QuinnInteractive\\Seo\\Tests\\": "tests/" - } - }, - "extra": { - "branch-alias": { - "dev-develop": "2.x-dev" + "autoload": { + "psr-4": { + "QuinnInteractive\\Seo\\": "src/", + "QuinnInteractive\\Seo\\Tests\\": "tests/" + } }, - "expose": [ - "dist" - ] - }, - "minimum-stability": "dev", - "prefer-stable": true + "extra": { + "branch-alias": { + "dev-develop": "3.x-dev" + }, + "expose": [ + "dist" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "allow-plugins": { + "composer/installers": true, + "silverstripe/vendor-plugin": true + } + } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 85eb950..8190532 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,6 @@ - + - tests + ./tests @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..cb14d8a --- /dev/null +++ b/rector.php @@ -0,0 +1,14 @@ +withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->withPhpSets(php81: true) + ->withImportNames(importShortClasses: false) + ->withTypeCoverageLevel(0); diff --git a/src/Analysis/Analysis.php b/src/Analysis/Analysis.php index e4c5df8..c34f30d 100644 --- a/src/Analysis/Analysis.php +++ b/src/Analysis/Analysis.php @@ -72,7 +72,7 @@ public function getContent() $parser = $this->getRenderedHtmlDomParser(); $output = []; foreach ($parser->find('p,h1,h2,h3,h4,h5') as $item) { - $output[] = strip_tags(html_entity_decode($item->innertext())); + $output[] = strip_tags(html_entity_decode((string) $item->innertext())); } $output = array_filter($output); diff --git a/src/Analysis/FocusKeywordContentAnalysis.php b/src/Analysis/FocusKeywordContentAnalysis.php index a5d8473..6016572 100644 --- a/src/Analysis/FocusKeywordContentAnalysis.php +++ b/src/Analysis/FocusKeywordContentAnalysis.php @@ -8,9 +8,9 @@ */ class FocusKeywordContentAnalysis extends Analysis { - const FOCUS_KEYWORD_NOT_FOUND = 0; - const FOCUS_KEYWORD_SUCCESS = 1; - const FOCUS_KEYWORD_UNSET = -1; + public const FOCUS_KEYWORD_NOT_FOUND = 0; + public const FOCUS_KEYWORD_SUCCESS = 1; + public const FOCUS_KEYWORD_UNSET = -1; private static $hidden_levels = [ 'default' diff --git a/src/Analysis/FocusKeywordUniqueAnalysis.php b/src/Analysis/FocusKeywordUniqueAnalysis.php index da2a801..1ce3aa8 100644 --- a/src/Analysis/FocusKeywordUniqueAnalysis.php +++ b/src/Analysis/FocusKeywordUniqueAnalysis.php @@ -8,9 +8,9 @@ */ class FocusKeywordUniqueAnalysis extends Analysis { - const FOCUS_KEYWORD_INUSE = 0; - const FOCUS_KEYWORD_SUCCESS = 1; - const FOCUS_KEYWORD_UNSET = -1; + public const FOCUS_KEYWORD_INUSE = 0; + public const FOCUS_KEYWORD_SUCCESS = 1; + public const FOCUS_KEYWORD_UNSET = -1; /** * @return string diff --git a/src/Analysis/FocusKeywordUrlAnalysis.php b/src/Analysis/FocusKeywordUrlAnalysis.php index d725cf8..ea71246 100644 --- a/src/Analysis/FocusKeywordUrlAnalysis.php +++ b/src/Analysis/FocusKeywordUrlAnalysis.php @@ -10,10 +10,10 @@ */ class FocusKeywordUrlAnalysis extends Analysis { - const FOCUS_KEYWORD_IRRELEVANT = -2; - const FOCUS_KEYWORD_NOT_IN_URL = 0; - const FOCUS_KEYWORD_SUCCESS = 1; - const FOCUS_KEYWORD_UNSET = -1; + public const FOCUS_KEYWORD_IRRELEVANT = -2; + public const FOCUS_KEYWORD_NOT_IN_URL = 0; + public const FOCUS_KEYWORD_SUCCESS = 1; + public const FOCUS_KEYWORD_UNSET = -1; private static $hidden_levels = [ 'default' @@ -38,19 +38,19 @@ public function responses() { return [ static::FOCUS_KEYWORD_IRRELEVANT => [ - _t( __CLASS__ . '.FOCUS_KEYWORD_IRRELEVANT', 'The focus keyword is irrelevant on the home page; this message will not display'), + _t( self::class . '.FOCUS_KEYWORD_IRRELEVANT', 'The focus keyword is irrelevant on the home page; this message will not display'), 'default' ], static::FOCUS_KEYWORD_UNSET => [ - _t( __CLASS__ . '.FOCUS_KEYWORD_UNSET', 'The focus keyword has not been set; consider setting this to improve content analysis'), + _t( self::class . '.FOCUS_KEYWORD_UNSET', 'The focus keyword has not been set; consider setting this to improve content analysis'), 'default' ], static::FOCUS_KEYWORD_NOT_IN_URL => [ - _t( __CLASS__ . '.FOCUS_KEYWORD_NOT_IN_URL', 'The focus keyword is not in the url segment; consider changing this and if you do SilverStripe will automatically redirect your old URL!'), + _t( self::class . '.FOCUS_KEYWORD_NOT_IN_URL', 'The focus keyword is not in the url segment; consider changing this and if you do SilverStripe will automatically redirect your old URL!'), 'warning' ], static::FOCUS_KEYWORD_SUCCESS => [ - _t( __CLASS__ . '.FOCUS_KEYWORD_SUCCESS', 'The focus keyword is in the url segment; this is great!'), + _t( self::class . '.FOCUS_KEYWORD_SUCCESS', 'The focus keyword is in the url segment; this is great!'), 'success' ], ]; diff --git a/src/Analysis/TitleAnalysis.php b/src/Analysis/TitleAnalysis.php index a10232f..8b8b557 100644 --- a/src/Analysis/TitleAnalysis.php +++ b/src/Analysis/TitleAnalysis.php @@ -8,13 +8,13 @@ */ class TitleAnalysis extends Analysis { - const TITLE_FOCUS_KEYWORD_POSITION = 4; - const TITLE_IS_HOME = -1; - const TITLE_NO_FOCUS_KEYWORD = 3; // only checked if the focus keyword has been defined - const TITLE_OK_BUT_SHORT = 1; - const TITLE_SUCCESS = 5; - const TITLE_TOO_LONG = 2; - const TITLE_TOO_SHORT = 0; + public const TITLE_FOCUS_KEYWORD_POSITION = 4; + public const TITLE_IS_HOME = -1; + public const TITLE_NO_FOCUS_KEYWORD = 3; // only checked if the focus keyword has been defined + public const TITLE_OK_BUT_SHORT = 1; + public const TITLE_SUCCESS = 5; + public const TITLE_TOO_LONG = 2; + public const TITLE_TOO_SHORT = 0; /** * @return array diff --git a/src/Analysis/WordCountAnalysis.php b/src/Analysis/WordCountAnalysis.php index def188e..4775358 100644 --- a/src/Analysis/WordCountAnalysis.php +++ b/src/Analysis/WordCountAnalysis.php @@ -10,8 +10,8 @@ */ class WordCountAnalysis extends Analysis { - const WORD_COUNT_ABOVE_MIN = 1; - const WORD_COUNT_BELOW_MIN = 0; + public const WORD_COUNT_ABOVE_MIN = 1; + public const WORD_COUNT_BELOW_MIN = 0; /** * @return int diff --git a/src/Builders/FacebookMetaGenerator.php b/src/Builders/FacebookMetaGenerator.php index 7638c25..5886495 100644 --- a/src/Builders/FacebookMetaGenerator.php +++ b/src/Builders/FacebookMetaGenerator.php @@ -105,11 +105,11 @@ public function process() $tags = []; if ($this->getTitle()) { - $tags[] = sprintf('', htmlentities($this->getTitle())); + $tags[] = sprintf('', htmlentities((string) $this->getTitle())); } if ($this->getDescription()) { - $tags[] = sprintf('', htmlentities($this->getDescription())); + $tags[] = sprintf('', htmlentities((string) $this->getDescription())); } if ($this->getType()) { @@ -136,11 +136,9 @@ public function process() } /** - * @param mixed $description - * * @return FacebookMetaGenerator */ - public function setDescription($description) + public function setDescription(mixed $description) { $this->description = $description; return $this; @@ -172,13 +170,11 @@ public function setImageHeight($height) } /** - * @param mixed $imageUrl - * * @return FacebookMetaGenerator */ - public function setImageUrl($imageUrl) + public function setImageUrl(mixed $imageUrl) { - if ($imageUrl && (substr($imageUrl, 0, 1) === '/' || substr($imageUrl, 0, 4) !== 'http')) { + if ($imageUrl && (str_starts_with((string) $imageUrl, '/') || !str_starts_with((string) $imageUrl, 'http'))) { throw new \InvalidArgumentException( 'A relative or invalid URL was detected; you must provide the full absolute URL' ); @@ -200,23 +196,20 @@ public function setImageWidth($width) } /** - * @param mixed $title - * * @return FacebookMetaGenerator */ - public function setTitle($title) + public function setTitle(mixed $title) { $this->title = $title; return $this; } /** - * @param mixed $type * * @return FacebookMetaGenerator * @throws \Exception */ - public function setType($type) + public function setType(mixed $type) { if (!in_array($type, array_keys(static::getValidTypes()))) { throw new \Exception(sprintf( @@ -231,13 +224,11 @@ public function setType($type) } /** - * @param mixed $url - * * @return FacebookMetaGenerator */ - public function setUrl($url) + public function setUrl(mixed $url) { - if ($url && (substr($url, 0, 1) === '/' || substr($url, 0, 4) !== 'http')) { + if ($url && (str_starts_with((string) $url, '/') || !str_starts_with((string) $url, 'http'))) { throw new \InvalidArgumentException( 'A relative URL was detected; you must provide the full absolute URL instead' ); diff --git a/src/Builders/TwitterMetaGenerator.php b/src/Builders/TwitterMetaGenerator.php index fcaa503..ee07a8e 100644 --- a/src/Builders/TwitterMetaGenerator.php +++ b/src/Builders/TwitterMetaGenerator.php @@ -95,11 +95,11 @@ public function process() $tags[] = ''; if ($this->getTitle()) { - $tags[] = sprintf('', htmlentities($this->getTitle())); + $tags[] = sprintf('', htmlentities((string) $this->getTitle())); } if ($this->getDescription()) { - $tags[] = sprintf('', htmlentities($this->getDescription())); + $tags[] = sprintf('', htmlentities((string) $this->getDescription())); } if ($this->getImageUrl()) { @@ -129,11 +129,9 @@ public function setCreator($creator) } /** - * @param mixed $description - * * @return TwitterMetaGenerator */ - public function setDescription($description) + public function setDescription(mixed $description) { $this->description = $description; @@ -141,13 +139,11 @@ public function setDescription($description) } /** - * @param mixed $imageUrl - * * @return TwitterMetaGenerator */ - public function setImageUrl($imageUrl) + public function setImageUrl(mixed $imageUrl) { - if ($imageUrl && (substr($imageUrl, 0, 1) === '/' || substr($imageUrl, 0, 4) !== 'http')) { + if ($imageUrl && (str_starts_with((string) $imageUrl, '/') || !str_starts_with((string) $imageUrl, 'http'))) { throw new \InvalidArgumentException( 'A relative or invalid URL was detected, your must provide the full absolute URL' ); @@ -158,11 +154,9 @@ public function setImageUrl($imageUrl) } /** - * @param mixed $title - * * @return TwitterMetaGenerator */ - public function setTitle($title) + public function setTitle(mixed $title) { $this->title = $title; diff --git a/src/Extensions/MemberExtension.php b/src/Extensions/MemberExtension.php index 388c7ed..26b7230 100644 --- a/src/Extensions/MemberExtension.php +++ b/src/Extensions/MemberExtension.php @@ -27,6 +27,8 @@ public function updateCMSFields(FieldList $fields) $fields->addFieldsToTab('Root.Main', [ TextField::create('TwitterAccountName') ], 'Password'); + } else { + $fields->removeByName('TwitterAccountName'); } } } diff --git a/src/Extensions/PageHealthExtension.php b/src/Extensions/PageHealthExtension.php index 7a37984..b22f487 100644 --- a/src/Extensions/PageHealthExtension.php +++ b/src/Extensions/PageHealthExtension.php @@ -2,6 +2,7 @@ namespace QuinnInteractive\Seo\Extensions; +use SilverStripe\ErrorPage\ErrorPage; use KubAT\PhpSimple\HtmlDomParser; use QuinnInteractive\Seo\Forms\GoogleSearchPreview; use QuinnInteractive\Seo\Forms\HealthAnalysisField; @@ -22,7 +23,13 @@ */ class PageHealthExtension extends DataExtension { - const EMPTY_HTML = '

'; + public const EMPTY_HTML = '

'; + + private static $tab_name = 'Root.SEO'; + + private static bool $move_default_meta_fields = true; + + private static bool $start_closed = true; /** * @var string|null @@ -103,14 +110,14 @@ public function updateCMSFields(FieldList $fields) return; } - if ($this->owner instanceof \SilverStripe\ErrorPage\ErrorPage) { + if (class_exists('\SilverStripe\ErrorPage\ErrorPage') && $this->owner instanceof ErrorPage) { return; } $dom = $this->getRenderedHtmlDomParser(); if ($dom) { - $fields->addFieldsToTab('Root.Main', [ + $fields->addFieldsToTab($this->owner->config()->get('tab_name'), [ ToggleCompositeField::create('SEOHealthAnalysis', 'SEO Health Analysis', [ GoogleSearchPreview::create( 'GoogleSearchPreview', @@ -120,8 +127,17 @@ public function updateCMSFields(FieldList $fields) ), TextField::create('FocusKeyword', 'Set focus keyword'), HealthAnalysisField::create('ContentAnalysis', 'Content Analysis', $this->getOwner()), - ]) + ])->setStartClosed($this->owner->config()->get('start_closed')) ], 'Metadata'); + + if ($this->owner->config()->get('move_default_meta_fields')) { + $meta = $fields->fieldByName('Root.Main.Metadata'); + + if ($meta) { + $fields->removeByName('Metadata'); + $fields->addFieldToTab($this->owner->config()->get('tab_name'), $meta); + } + } } } } diff --git a/src/Extensions/PageSeoExtension.php b/src/Extensions/PageSeoExtension.php index 556c460..1099f18 100644 --- a/src/Extensions/PageSeoExtension.php +++ b/src/Extensions/PageSeoExtension.php @@ -35,6 +35,12 @@ class PageSeoExtension extends DataExtension { use Configurable; + private static string $tab_name = 'Root.SEO'; + + private static bool $start_closed = true; + + private static bool $use_composite_field = true; + private static $cascade_deletes = [ 'FacebookPageImage', 'TwitterPageImage' @@ -74,7 +80,7 @@ class PageSeoExtension extends DataExtension */ public function MetaTags(&$tags) { - $tags = explode(PHP_EOL, $tags); + $tags = explode(PHP_EOL, (string) $tags); $tags = array_merge( $tags, Seo::getCanonicalUrlLink($this->getOwner()), @@ -103,51 +109,68 @@ public function onBeforeWrite() public function updateCMSFields(FieldList $fields) { parent::updateCMSFields($fields); + $suppressMessaging = false; + if (Controller::curr() instanceof HistoryViewerController) { // avoid cluttering the history comparison UI $suppressMessaging = true; } - $fields->addFieldsToTab('Root.Main', [ - ToggleCompositeField::create('FacebookSeoComposite', 'Facebook SEO', [ - DropdownField::create('FacebookPageType', 'Type', FacebookMetaGenerator::getValidTypes()), - TextField::create('FacebookPageTitle', 'Title') - ->setAttribute('placeholder', $this->getOwner()->Title) - ->setRightTitle($suppressMessaging ? '' : 'If blank, inherits default page title') - ->setTargetLength(45, 25, 70), - UploadField::create('FacebookPageImage', 'Image') - ->setRightTitle($suppressMessaging - ? '' - : 'Facebook recommends images to be 1200 x 630 pixels. ' . - 'If no image is provided, Facebook will choose the first image that appears on the page, ' . - 'which usually has bad results') - ->setFolderName('seo'), - TextareaField::create('FacebookPageDescription', 'Description') - ->setAttribute('placeholder', $this->getOwner()->MetaDescription ?: - $this->getOwner()->dbObject('Content')->LimitCharacters(297)) - ->setRightTitle($suppressMessaging - ? '' - : 'If blank, inherits meta description if it exists ' . - 'or gets the first 297 characters from content') - ->setTargetLength(200, 160, 320), - ]), - ToggleCompositeField::create('TwitterSeoComposite', 'Twitter SEO', [ - TextField::create('TwitterPageTitle', 'Title') - ->setAttribute('placeholder', $this->getOwner()->Title) - ->setRightTitle($suppressMessaging ? '' : 'If blank, inherits default page title') - ->setTargetLength(45, 25, 70), - UploadField::create('TwitterPageImage', 'Image') - ->setRightTitle($suppressMessaging ? '' : 'Must be at least 280x150 pixels') - ->setFolderName('seo'), - TextareaField::create('TwitterPageDescription', 'Description') - ->setAttribute('placeholder', $this->getOwner()->MetaDescription ?: - $this->getOwner()->dbObject('Content')->LimitCharacters(297)) - ->setRightTitle($suppressMessaging - ? '' - : 'If blank, inherits meta description if it exists ' . + $openGraphFields = [ + DropdownField::create('FacebookPageType', 'Type', FacebookMetaGenerator::getValidTypes()), + TextField::create('FacebookPageTitle', 'Title') + ->setAttribute('placeholder', $this->getOwner()->Title) + ->setRightTitle($suppressMessaging ? '' : 'If blank, inherits default page title') + ->setTargetLength(45, 25, 70), + UploadField::create('FacebookPageImage', 'Image') + ->setRightTitle($suppressMessaging + ? '' + : 'Facebook recommends images to be 1200 x 630 pixels. ' . + 'If no image is provided, Facebook will choose the first image that appears on the page, ' . + 'which usually has bad results') + ->setFolderName('seo'), + TextareaField::create('FacebookPageDescription', 'Description') + ->setAttribute('placeholder', $this->getOwner()->MetaDescription ?: + $this->getOwner()->dbObject('Content')->LimitCharacters(297)) + ->setRightTitle($suppressMessaging + ? '' + : 'If blank, inherits meta description if it exists ' . + 'or gets the first 297 characters from content') + ->setTargetLength(200, 160, 320), + ]; + + + $fields->addFieldsToTab( + $this->config()->get('tab_name'), + $this->config()->get('use_composite_field') ? [ + ToggleCompositeField::create('FacebookSeoComposite', 'Open Graph', $openGraphFields) + ->setStartClosed($this->config()->get('start_closed')), + ] : $openGraphFields, + 'Metadata' + ); + + $fields->addFieldsToTab( + $this->config()->get('tab_name'), + [ + ToggleCompositeField::create('TwitterSeoComposite', 'Twitter SEO', [ + TextField::create('TwitterPageTitle', 'Title') + ->setAttribute('placeholder', $this->getOwner()->Title) + ->setRightTitle($suppressMessaging ? '' : 'If blank, inherits default page title') + ->setTargetLength(45, 25, 70), + UploadField::create('TwitterPageImage', 'Image') + ->setRightTitle($suppressMessaging ? '' : 'Must be at least 280x150 pixels') + ->setFolderName('seo'), + TextareaField::create('TwitterPageDescription', 'Description') + ->setAttribute('placeholder', $this->getOwner()->MetaDescription ?: + $this->getOwner()->dbObject('Content')->LimitCharacters(297)) + ->setRightTitle($suppressMessaging + ? '' + : 'If blank, inherits meta description if it exists ' . 'or gets the first 297 characters from content') - ->setTargetLength(200, 160, 320), - ]) - ], 'Metadata'); + ->setTargetLength(200, 160, 320), + ]) + ], + 'Metadata' + ); } } diff --git a/src/Extensions/SiteConfigSettingsExtension.php b/src/Extensions/SiteConfigSettingsExtension.php index 4a3f714..be08cdf 100644 --- a/src/Extensions/SiteConfigSettingsExtension.php +++ b/src/Extensions/SiteConfigSettingsExtension.php @@ -24,6 +24,8 @@ class SiteConfigSettingsExtension extends DataExtension { use Configurable; + private static string $tab_name = 'Root.SEO'; + private static $casting = [ 'GoogleAnalytics' => 'HTMLText' ]; @@ -44,7 +46,7 @@ public function updateCMSFields(FieldList $fields) $snPixelHelp = 'https://businesshelp.snapchat.com/en-US/article/snap-pixel'; $gaHelp = 'https://support.google.com/analytics/answer/1008080?hl=en'; - $fields->addFieldsToTab('Root.SEO', [ + $fields->addFieldsToTab($this->config()->get('tab_name'), [ TextField::create('TwitterAccountName'), TextareaField::create('GoogleAnalytics', 'Google Analytics')->setRightTitle($this->getHelpLink($gaHelp)), ToggleCompositeField::create(null, 'Pixels', [ diff --git a/src/Forms/GoogleSearchPreview.php b/src/Forms/GoogleSearchPreview.php index 3648abe..60133be 100644 --- a/src/Forms/GoogleSearchPreview.php +++ b/src/Forms/GoogleSearchPreview.php @@ -2,6 +2,7 @@ namespace QuinnInteractive\Seo\Forms; +use SilverStripe\Forms\FormField; use QuinnInteractive\Seo\Extensions\PageHealthExtension; use QuinnInteractive\Seo\Extensions\PageSeoExtension; use SilverStripe\Control\Controller; @@ -32,7 +33,7 @@ class GoogleSearchPreview extends LiteralField * HealthAnalysisField constructor. * * @param string $name - * @param \SilverStripe\Forms\FormField|string $title + * @param FormField|string $title * @param \Page|PageHealthExtension|PageSeoExtension $page * @param simple_html_dom $domParser */ @@ -91,10 +92,10 @@ public function limitDescriptionLength($text) { public function highlight($haystack, $needle) { if (!$needle) { - return strip_tags($haystack); + return strip_tags((string) $haystack); } - return preg_replace('/\b(' . $needle . ')\b/i', '$0', strip_tags($haystack)); + return preg_replace('/\b(' . $needle . ')\b/i', '$0', strip_tags((string) $haystack)); } /** diff --git a/src/Forms/HealthAnalysisField.php b/src/Forms/HealthAnalysisField.php index fc7a858..5925120 100644 --- a/src/Forms/HealthAnalysisField.php +++ b/src/Forms/HealthAnalysisField.php @@ -2,6 +2,7 @@ namespace QuinnInteractive\Seo\Forms; +use SilverStripe\Forms\FormField; use QuinnInteractive\Seo\Analysis\Analysis; use QuinnInteractive\Seo\Extensions\PageHealthExtension; use SilverStripe\CMS\Model\SiteTree; @@ -35,7 +36,7 @@ class HealthAnalysisField extends LiteralField * HealthAnalysisField constructor. * * @param string $name - * @param \SilverStripe\Forms\FormField|string $title + * @param FormField|string $title * @param \Page $page */ public function __construct($name, $title, SiteTree $page) diff --git a/src/Seo.php b/src/Seo.php index 00296a8..bcfb654 100644 --- a/src/Seo.php +++ b/src/Seo.php @@ -111,7 +111,7 @@ public static function getFacebookMetaTags($owner) $generator = FacebookMetaGenerator::create(); $generator->setTitle($owner->FacebookPageTitle ?: $owner->Title); - $generator->setDescription($owner->FacebookPageDescription ?: $owner->MetaDescription ?: $owner->Content); + $generator->setDescription(($owner->FacebookPageDescription ?: $owner->MetaDescription) ?: $owner->Content); $generator->setImageUrl(($owner->FacebookPageImage()->exists()) ? $owner->FacebookPageImage()->AbsoluteLink() : null); @@ -144,7 +144,7 @@ public static function getPixels() $ours = array_keys(SiteConfigSettingsExtension::config()->get('db')); $db = SiteConfig::config()->get('db'); foreach ($db as $k => $v) { - if (strstr($k, 'Pixel') && in_array($k, $ours)) { + if (strstr((string) $k, 'Pixel') && in_array($k, $ours)) { if (is_null($siteConfig->{$k})) { continue; } @@ -166,7 +166,7 @@ public static function getTwitterMetaTags($owner) { $generator = TwitterMetaGenerator::create(); $generator->setTitle($owner->TwitterPageTitle ?: $owner->Title); - $generator->setDescription($owner->TwitterPageDescription ?: $owner->MetaDescription ?: $owner->Content); + $generator->setDescription(($owner->TwitterPageDescription ?: $owner->MetaDescription) ?: $owner->Content); $generator->setImageUrl(($owner->TwitterPageImage()->exists()) ? $owner->TwitterPageImage()->AbsoluteLink() : null); diff --git a/tests/.gitkeep b/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Extensions/PageSeoExtensionTest.php b/tests/Extensions/PageSeoExtensionTest.php index d4a5416..b3937be 100644 --- a/tests/Extensions/PageSeoExtensionTest.php +++ b/tests/Extensions/PageSeoExtensionTest.php @@ -15,7 +15,7 @@ class PageSeoExtensionTest extends FunctionalTest /** @var \Page|PageSeoExtension */ protected $page; - public function setUp() + public function setUp(): void { parent::setUp(); diff --git a/tests/Pages.yml b/tests/Pages.yml index 44d6900..16a8adb 100644 --- a/tests/Pages.yml +++ b/tests/Pages.yml @@ -10,4 +10,4 @@ Page: URLSegment: industrial-panel-tanks Content: Something awesome about industrial panel tanks FocusKeyword: industrial panel tanks - MetaDescrpition: some long meta description that contains the focus keyword industrial panel tanks \ No newline at end of file + MetaDescrpition: some long meta description that contains the focus keyword industrial panel tanks diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..529755b --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,23 @@ + 'Varchar' + ]; + } +} + +if (!class_exists('PageController') && class_exists(ContentController::class)) { + class PageController extends ContentController + { + } +} + +require_once dirname(__DIR__) . '/vendor/silverstripe/framework/tests/bootstrap.php';