From 3280a934cf6f801970c0cf9973fde3d7fad1c445 Mon Sep 17 00:00:00 2001 From: Menno van den Ende <50165380+menno-ll@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:07:54 +0100 Subject: [PATCH 001/313] Include pluggable functions in shutdown callback --- admin/class-yoast-notification-center.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/admin/class-yoast-notification-center.php b/admin/class-yoast-notification-center.php index fcbc734d097..7ed3754a181 100644 --- a/admin/class-yoast-notification-center.php +++ b/admin/class-yoast-notification-center.php @@ -600,6 +600,12 @@ private function split_on_user_id( $notifications ) { * @return void */ public function update_storage() { + /** + * Plugins might exit on the plugins_loaded hook. + * This prevents the pluggable.php file from loading, as it's loaded after the plugins_loaded hook. + * As we need functions defined in pluggable.php, make sure it's loaded. + */ + require ABSPATH . WPINC . '/pluggable.php'; $notifications = $this->notifications; From a2f128a2bda4a141b33437b43afeb00d4e7d66e8 Mon Sep 17 00:00:00 2001 From: Menno van den Ende <50165380+menno-ll@users.noreply.github.com> Date: Fri, 19 Jan 2024 18:04:42 +0100 Subject: [PATCH 002/313] Update admin/class-yoast-notification-center.php Co-authored-by: Juliette <663378+jrfnl@users.noreply.github.com> --- admin/class-yoast-notification-center.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/class-yoast-notification-center.php b/admin/class-yoast-notification-center.php index 7ed3754a181..73323e95060 100644 --- a/admin/class-yoast-notification-center.php +++ b/admin/class-yoast-notification-center.php @@ -605,7 +605,7 @@ public function update_storage() { * This prevents the pluggable.php file from loading, as it's loaded after the plugins_loaded hook. * As we need functions defined in pluggable.php, make sure it's loaded. */ - require ABSPATH . WPINC . '/pluggable.php'; + require_once ABSPATH . WPINC . '/pluggable.php'; $notifications = $this->notifications; From 138b0b7e271ec417d13559cbb3f4398023021af4 Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Thu, 11 Jan 2024 12:13:45 +0100 Subject: [PATCH 003/313] Refactor classes to use newer syntax with `class` keyword --- packages/js/src/insights/initializer.js | 2 +- .../values/ProminentWord.js | 163 ++--- .../yoastseo/src/scoring/taxonomyAssessor.js | 61 +- .../yoastseo/src/values/AssessmentResult.js | 529 ++++++++-------- packages/yoastseo/src/values/Mark.js | 463 +++++++------- packages/yoastseo/src/values/Paper.js | 595 +++++++++--------- .../yoastseo/src/worker/AnalysisWebWorker.js | 2 +- .../yoastseo/src/worker/transporter/parse.js | 4 +- 8 files changed, 917 insertions(+), 902 deletions(-) diff --git a/packages/js/src/insights/initializer.js b/packages/js/src/insights/initializer.js index 9c8b21169aa..d07463b6e7d 100644 --- a/packages/js/src/insights/initializer.js +++ b/packages/js/src/insights/initializer.js @@ -23,7 +23,7 @@ const createUpdater = () => { * @returns {void} */ return () => { - const paper = Paper.parse( collectData() ); + const paper = Paper.prototype.parse( collectData() ); runResearch( "readingTime", paper ).then( response => setEstimatedReadingTime( response.result ) ); runResearch( "getFleschReadingScore", paper ).then( response => { diff --git a/packages/yoastseo/src/languageProcessing/values/ProminentWord.js b/packages/yoastseo/src/languageProcessing/values/ProminentWord.js index 49a994eb188..d190083b187 100644 --- a/packages/yoastseo/src/languageProcessing/values/ProminentWord.js +++ b/packages/yoastseo/src/languageProcessing/values/ProminentWord.js @@ -1,90 +1,95 @@ /** * Represents a prominent word in the context of relevant words. - * - * @constructor - * - * @param {string} word The word. - * @param {string} [stem] The stem / base form of the word, defaults to the word. - * @param {number} [occurrences] The number of occurrences, defaults to 0. - */ -function ProminentWord( word, stem, occurrences ) { - this._word = word; - this._stem = stem ? stem : word; - this._occurrences = occurrences || 0; -} +*/ +class ProminentWord { + /** + * Constructs Prominent word object. + * + * @constructor + * + * @param {string} word The word. + * @param {string} [stem] The stem / base form of the word, defaults to the word. + * @param {number} [occurrences] The number of occurrences, defaults to 0. + */ + constructor( word, stem, occurrences ) { + this._word = word; + this._stem = stem ? stem : word; + this._occurrences = occurrences || 0; + } -/** - * Sets the word. - * - * @param {string} word The word to set. - * - * @returns {void}. - */ -ProminentWord.prototype.setWord = function( word ) { - this._word = word; -}; + /** + * Sets the word. + * + * @param {string} word The word to set. + * + * @returns {void}. + */ + setWord( word ) { + this._word = word; + } -/** - * Returns the word. - * - * @returns {string} The word. - */ -ProminentWord.prototype.getWord = function() { - return this._word; -}; + /** + * Returns the word. + * + * @returns {string} The word. + */ + getWord() { + return this._word; + } -/** - * Returns the stem of the word. - * - * @returns {string} The stem. - */ -ProminentWord.prototype.getStem = function() { - return this._stem; -}; + /** + * Returns the stem of the word. + * + * @returns {string} The stem. + */ + getStem() { + return this._stem; + } -/** - * Sets the number of occurrences to the word. - * - * @param {int} numberOfOccurrences The number of occurrences to set. - * - * @returns {void}. - */ -ProminentWord.prototype.setOccurrences = function( numberOfOccurrences ) { - this._occurrences = numberOfOccurrences; -}; + /** + * Sets the number of occurrences to the word. + * + * @param {int} numberOfOccurrences The number of occurrences to set. + * + * @returns {void}. + */ + setOccurrences( numberOfOccurrences ) { + this._occurrences = numberOfOccurrences; + } -/** - * Returns the amount of occurrences of this word. - * - * @returns {number} The number of occurrences. - */ -ProminentWord.prototype.getOccurrences = function() { - return this._occurrences; -}; + /** + * Returns the amount of occurrences of this word. + * + * @returns {number} The number of occurrences. + */ + getOccurrences() { + return this._occurrences; + } -/** - * Serializes the ProminentWord instance to an object. - * - * @returns {Object} The serialized ProminentWord. - */ -ProminentWord.prototype.serialize = function() { - return { - _parseClass: "ProminentWord", - word: this._word, - stem: this._stem, - occurrences: this._occurrences, - }; -}; + /** + * Serializes the ProminentWord instance to an object. + * + * @returns {Object} The serialized ProminentWord. + */ + serialize() { + return { + _parseClass: "ProminentWord", + word: this._word, + stem: this._stem, + occurrences: this._occurrences, + }; + } -/** - * Parses the object to a ProminentWord. - * - * @param {Object} serialized The serialized object. - * - * @returns {ProminentWord} The parsed ProminentWord. - */ -ProminentWord.parse = function( serialized ) { - return new ProminentWord( serialized.word, serialized.stem, serialized.occurrences ); -}; + /** + * Parses the object to a ProminentWord. + * + * @param {Object} serialized The serialized object. + * + * @returns {ProminentWord} The parsed ProminentWord. + */ + parse( serialized ) { + return new ProminentWord( serialized.word, serialized.stem, serialized.occurrences ); + } +} export default ProminentWord; diff --git a/packages/yoastseo/src/scoring/taxonomyAssessor.js b/packages/yoastseo/src/scoring/taxonomyAssessor.js index 8801a98728e..d682e36a487 100644 --- a/packages/yoastseo/src/scoring/taxonomyAssessor.js +++ b/packages/yoastseo/src/scoring/taxonomyAssessor.js @@ -1,5 +1,3 @@ -import { inherits } from "util"; - import IntroductionKeywordAssessment from "./assessments/seo/IntroductionKeywordAssessment"; import KeyphraseLengthAssessment from "./assessments/seo/KeyphraseLengthAssessment"; import KeyphraseDensityAssessment from "./assessments/seo/KeywordDensityAssessment"; @@ -33,36 +31,39 @@ export const getTextLengthAssessment = function() { /** * Creates the Assessor used for taxonomy pages. - * - * @param {Researcher} researcher The researcher used for the analysis. - * @param {Object?} options The options for this assessor. - * @constructor */ -const TaxonomyAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "taxonomyAssessor"; +class TaxonomyAssessor extends Assessor { + /** + * Creates a new taxonomy assessor. + * + * @param {Researcher} researcher The researcher to use. + * @param {Object} options The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); - this._assessments = [ - new IntroductionKeywordAssessment(), - new KeyphraseLengthAssessment(), - new KeyphraseDensityAssessment(), - new MetaDescriptionKeywordAssessment(), - new MetaDescriptionLengthAssessment(), - getTextLengthAssessment(), - new KeyphraseInSEOTitleAssessment(), - new PageTitleWidthAssessment( - { - scores: { - widthTooShort: 9, - }, - }, true - ), - new SlugKeywordAssessment(), - new FunctionWordsInKeyphrase(), - new SingleH1Assessment(), - ]; -}; + this.type = "taxonomyAssessor"; -inherits( TaxonomyAssessor, Assessor ); + this._assessments = [ + new IntroductionKeywordAssessment(), + new KeyphraseLengthAssessment(), + new KeyphraseDensityAssessment(), + new MetaDescriptionKeywordAssessment(), + new MetaDescriptionLengthAssessment(), + getTextLengthAssessment(), + new KeyphraseInSEOTitleAssessment(), + new PageTitleWidthAssessment( + { + scores: { + widthTooShort: 9, + }, + }, true + ), + new SlugKeywordAssessment(), + new FunctionWordsInKeyphrase(), + new SingleH1Assessment(), + ]; + } +} export default TaxonomyAssessor; diff --git a/packages/yoastseo/src/values/AssessmentResult.js b/packages/yoastseo/src/values/AssessmentResult.js index 6b6533993e9..489ea640a8d 100644 --- a/packages/yoastseo/src/values/AssessmentResult.js +++ b/packages/yoastseo/src/values/AssessmentResult.js @@ -7,308 +7,311 @@ import Mark from "./Mark"; * * @returns {Array} A list of empty marks. */ -var emptyMarker = function() { +const emptyMarker = function() { return []; }; /** - * Construct the AssessmentResult value object. - * - * @param {Object} [values] The values for this assessment result. - * - * @constructor + * Represents the assessment result. */ -var AssessmentResult = function( values ) { - this._hasScore = false; - this._identifier = ""; - this._hasMarks = false; - this._hasJumps = false; - this._hasEditFieldName = false; - this._marker = emptyMarker; - this._hasBetaBadge = false; - this.score = 0; - this.text = ""; - this.marks = []; - this.editFieldName = ""; - - if ( isUndefined( values ) ) { - values = {}; +class AssessmentResult { + /** + * Constructs the AssessmentResult value object. + * + * @param {Object} [values] The values for this assessment result. + */ + constructor( values ) { + this._hasScore = false; + this._identifier = ""; + this._hasMarks = false; + this._hasJumps = false; + this._hasEditFieldName = false; + this._marker = emptyMarker; + this._hasBetaBadge = false; + this.score = 0; + this.text = ""; + this.marks = []; + this.editFieldName = ""; + + if ( isUndefined( values ) ) { + values = {}; + } + + if ( ! isUndefined( values.score ) ) { + this.setScore( values.score ); + } + + if ( ! isUndefined( values.text ) ) { + this.setText( values.text ); + } + + if ( ! isUndefined( values.marks ) ) { + this.setMarks( values.marks ); + } + + if ( ! isUndefined( values._hasBetaBadge ) ) { + this.setHasBetaBadge( values._hasBetaBadge ); + } + + if ( ! isUndefined( values._hasJumps ) ) { + this.setHasJumps( values._hasJumps ); + } + + if ( ! isUndefined( values.editFieldName ) ) { + this.setEditFieldName( values.editFieldName ); + } } - if ( ! isUndefined( values.score ) ) { - this.setScore( values.score ); + /** + * Checks if a score is available. + * @returns {boolean} Whether or not a score is available. + */ + hasScore() { + return this._hasScore; } - if ( ! isUndefined( values.text ) ) { - this.setText( values.text ); + /** + * Gets the available score + * @returns {number} The score associated with the AssessmentResult. + */ + getScore() { + return this.score; } - if ( ! isUndefined( values.marks ) ) { - this.setMarks( values.marks ); + /** + * Sets the score for the assessment. + * @param {number} score The score to be used for the score property + * @returns {void} + */ + setScore( score ) { + if ( isNumber( score ) ) { + this.score = score; + this._hasScore = true; + } } - if ( ! isUndefined( values._hasBetaBadge ) ) { - this.setHasBetaBadge( values._hasBetaBadge ); + /** + * Checks if a text is available. + * @returns {boolean} Whether or not a text is available. + */ + hasText() { + return this.text !== ""; } - if ( ! isUndefined( values._hasJumps ) ) { - this.setHasJumps( values._hasJumps ); + /** + * Gets the available text + * @returns {string} The text associated with the AssessmentResult. + */ + getText() { + return this.text; } - if ( ! isUndefined( values.editFieldName ) ) { - this.setEditFieldName( values.editFieldName ); + /** + * Sets the text for the assessment. + * @param {string} text The text to be used for the text property + * @returns {void} + */ + setText( text ) { + if ( isUndefined( text ) ) { + text = ""; + } + + this.text = text; } -}; - -/** - * Check if a score is available. - * @returns {boolean} Whether or not a score is available. - */ -AssessmentResult.prototype.hasScore = function() { - return this._hasScore; -}; -/** - * Get the available score - * @returns {number} The score associated with the AssessmentResult. - */ -AssessmentResult.prototype.getScore = function() { - return this.score; -}; - -/** - * Set the score for the assessment. - * @param {number} score The score to be used for the score property - * @returns {void} - */ -AssessmentResult.prototype.setScore = function( score ) { - if ( isNumber( score ) ) { - this.score = score; - this._hasScore = true; + /** + * Gets the available marks. + * + * @returns {array} The marks associated with the AssessmentResult. + */ + getMarks() { + return this.marks; } -}; -/** - * Check if a text is available. - * @returns {boolean} Whether or not a text is available. - */ -AssessmentResult.prototype.hasText = function() { - return this.text !== ""; -}; - -/** - * Get the available text - * @returns {string} The text associated with the AssessmentResult. - */ -AssessmentResult.prototype.getText = function() { - return this.text; -}; - -/** - * Set the text for the assessment. - * @param {string} text The text to be used for the text property - * @returns {void} - */ -AssessmentResult.prototype.setText = function( text ) { - if ( isUndefined( text ) ) { - text = ""; + /** + * Sets the marks for the assessment. + * + * @param {array} marks The marks to be used for the marks property + * + * @returns {void} + */ + setMarks( marks ) { + if ( isArray( marks ) ) { + this.marks = marks; + this._hasMarks = marks.length > 0; + } } - this.text = text; -}; - -/** - * Gets the available marks. - * - * @returns {array} The marks associated with the AssessmentResult. - */ -AssessmentResult.prototype.getMarks = function() { - return this.marks; -}; - -/** - * Sets the marks for the assessment. - * - * @param {array} marks The marks to be used for the marks property - * - * @returns {void} - */ -AssessmentResult.prototype.setMarks = function( marks ) { - if ( isArray( marks ) ) { - this.marks = marks; - this._hasMarks = marks.length > 0; + /** + * Sets the identifier + * + * @param {string} identifier An alphanumeric identifier for this result. + * @returns {void} + */ + setIdentifier( identifier ) { + this._identifier = identifier; } -}; -/** - * Sets the identifier - * - * @param {string} identifier An alphanumeric identifier for this result. - * @returns {void} - */ -AssessmentResult.prototype.setIdentifier = function( identifier ) { - this._identifier = identifier; -}; - -/** - * Gets the identifier - * - * @returns {string} An alphanumeric identifier for this result. - */ -AssessmentResult.prototype.getIdentifier = function() { - return this._identifier; -}; + /** + * Gets the identifier + * + * @returns {string} An alphanumeric identifier for this result. + */ + getIdentifier() { + return this._identifier; + } -/** - * Sets the marker, a pure function that can return the marks for a given Paper - * - * @param {Function} marker The marker to set. - * @returns {void} - */ -AssessmentResult.prototype.setMarker = function( marker ) { - this._marker = marker; -}; + /** + * Sets the marker, a pure function that can return the marks for a given Paper + * + * @param {Function} marker The marker to set. + * @returns {void} + */ + setMarker( marker ) { + this._marker = marker; + } -/** - * Returns whether or not this result has a marker that can be used to mark for a given Paper - * - * @returns {boolean} Whether or this result has a marker. - */ -AssessmentResult.prototype.hasMarker = function() { - return this._hasMarks && this._marker !== this.emptyMarker; -}; + /** + * Returns whether or not this result has a marker that can be used to mark for a given Paper + * + * @returns {boolean} Whether or this result has a marker. + */ + hasMarker() { + return this._hasMarks && this._marker !== this.emptyMarker; + } -/** - * Gets the marker, a pure function that can return the marks for a given Paper - * - * @returns {Function} The marker. - */ -AssessmentResult.prototype.getMarker = function() { - return this._marker; -}; + /** + * Gets the marker, a pure function that can return the marks for a given Paper + * + * @returns {Function} The marker. + */ + getMarker() { + return this._marker; + } -/** - * Sets the value of _hasMarks to determine if there is something to mark. - * - * @param {boolean} hasMarks Is there something to mark. - * @returns {void} - */ -AssessmentResult.prototype.setHasMarks = function( hasMarks ) { - this._hasMarks = hasMarks; -}; + /** + * Sets the value of _hasMarks to determine if there is something to mark. + * + * @param {boolean} hasMarks Is there something to mark. + * @returns {void} + */ + setHasMarks( hasMarks ) { + this._hasMarks = hasMarks; + } -/** - * Returns the value of _hasMarks to determine if there is something to mark. - * - * @returns {boolean} Is there something to mark. - */ -AssessmentResult.prototype.hasMarks = function() { - return this._hasMarks; -}; + /** + * Returns the value of _hasMarks to determine if there is something to mark. + * + * @returns {boolean} Is there something to mark. + */ + hasMarks() { + return this._hasMarks; + } -/** - * Sets the value of _hasBetaBadge to determine if the result has a beta badge. - * - * @param {boolean} hasBetaBadge Whether this result has a beta badge. - * @returns {void} - */ -AssessmentResult.prototype.setHasBetaBadge = function( hasBetaBadge ) { - this._hasBetaBadge = hasBetaBadge; -}; + /** + * Sets the value of _hasBetaBadge to determine if the result has a beta badge. + * + * @param {boolean} hasBetaBadge Whether this result has a beta badge. + * @returns {void} + */ + setHasBetaBadge( hasBetaBadge ) { + this._hasBetaBadge = hasBetaBadge; + } -/** - * Returns the value of _hasBetaBadge to determine if the result has a beta badge. - * - * @returns {bool} Whether this result has a beta badge. - */ -AssessmentResult.prototype.hasBetaBadge = function() { - return this._hasBetaBadge; -}; + /** + * Returns the value of _hasBetaBadge to determine if the result has a beta badge. + * + * @returns {bool} Whether this result has a beta badge. + */ + hasBetaBadge() { + return this._hasBetaBadge; + } -/** - * Sets the value of _hasJumps to determine whether it's needed to jump to a different field. - * - * @param {boolean} hasJumps Whether this result causes a jump to a different field. - * @returns {void} - */ -AssessmentResult.prototype.setHasJumps = function( hasJumps ) { - this._hasJumps = hasJumps; -}; + /** + * Sets the value of _hasJumps to determine whether it's needed to jump to a different field. + * + * @param {boolean} hasJumps Whether this result causes a jump to a different field. + * @returns {void} + */ + setHasJumps( hasJumps ) { + this._hasJumps = hasJumps; + } -/** - * Returns the value of _hasJumps to determine whether it's needed to jump to a different field. - * - * @returns {bool} Whether this result causes a jump to a different field. - */ -AssessmentResult.prototype.hasJumps = function() { - return this._hasJumps; -}; + /** + * Returns the value of _hasJumps to determine whether it's needed to jump to a different field. + * + * @returns {bool} Whether this result causes a jump to a different field. + */ + hasJumps() { + return this._hasJumps; + } -/** - * Check if an edit field name is available. - * @returns {boolean} Whether or not an edit field name is available. - */ -AssessmentResult.prototype.hasEditFieldName = function() { - return this._hasEditFieldName; -}; + /** + * Check if an edit field name is available. + * @returns {boolean} Whether or not an edit field name is available. + */ + hasEditFieldName() { + return this._hasEditFieldName; + } -/** - * Get the edit field name. - * @returns {string} The edit field name associated with the AssessmentResult. - */ -AssessmentResult.prototype.getEditFieldName = function() { - return this.editFieldName; -}; + /** + * Gets the edit field name. + * @returns {string} The edit field name associated with the AssessmentResult. + */ + getEditFieldName() { + return this.editFieldName; + } -/** - * Set the edit field name to be used to create the aria label for an edit button. - * @param {string} editFieldName The string to be used for the string property - * @returns {void} - */ -AssessmentResult.prototype.setEditFieldName = function( editFieldName ) { - if ( editFieldName !== "" ) { - this.editFieldName = editFieldName; - this._hasEditFieldName = true; + /** + * Sets the edit field name to be used to create the aria label for an edit button. + * @param {string} editFieldName The string to be used for the string property + * @returns {void} + */ + setEditFieldName( editFieldName ) { + if ( editFieldName !== "" ) { + this.editFieldName = editFieldName; + this._hasEditFieldName = true; + } } -}; -/** - * Serializes the AssessmentResult instance to an object. - * - * @returns {Object} The serialized AssessmentResult. - */ -AssessmentResult.prototype.serialize = function() { - return { - _parseClass: "AssessmentResult", - identifier: this._identifier, - score: this.score, - text: this.text, - marks: this.marks.map( mark => mark.serialize() ), - _hasBetaBadge: this._hasBetaBadge, - _hasJumps: this._hasJumps, - editFieldName: this.editFieldName, - }; -}; + /** + * Serializes the AssessmentResult instance to an object. + * + * @returns {Object} The serialized AssessmentResult. + */ + serialize() { + return { + _parseClass: "AssessmentResult", + identifier: this._identifier, + score: this.score, + text: this.text, + marks: this.marks.map( mark => mark.serialize() ), + _hasBetaBadge: this._hasBetaBadge, + _hasJumps: this._hasJumps, + editFieldName: this.editFieldName, + }; + } -/** - * Parses the object to an AssessmentResult. - * - * @param {Object} serialized The serialized object. - * - * @returns {AssessmentResult} The parsed AssessmentResult. - */ -AssessmentResult.parse = function( serialized ) { - const result = new AssessmentResult( { - text: serialized.text, - score: serialized.score, - marks: serialized.marks.map( mark => Mark.parse( mark ) ), - _hasBetaBadge: serialized._hasBetaBadge, - _hasJumps: serialized._hasJumps, - editFieldName: serialized.editFieldName, - } ); - result.setIdentifier( serialized.identifier ); - - return result; -}; + /** + * Parses the object to an AssessmentResult. + * + * @param {Object} serialized The serialized object. + * + * @returns {AssessmentResult} The parsed AssessmentResult. + */ + parse( serialized ) { + const result = new AssessmentResult( { + text: serialized.text, + score: serialized.score, + marks: serialized.marks.map( mark => Mark.parse( mark ) ), + _hasBetaBadge: serialized._hasBetaBadge, + _hasJumps: serialized._hasJumps, + editFieldName: serialized.editFieldName, + } ); + result.setIdentifier( serialized.identifier ); + + return result; + } +} export default AssessmentResult; diff --git a/packages/yoastseo/src/values/Mark.js b/packages/yoastseo/src/values/Mark.js index bcbbd71f350..7b87570a384 100644 --- a/packages/yoastseo/src/values/Mark.js +++ b/packages/yoastseo/src/values/Mark.js @@ -1,264 +1,269 @@ import { defaults, isUndefined } from "lodash-es"; -/** - * Represents a place where highlighting should be applied. - * We allow both replacement-based highlighting (through providing `original`, `marked`, and potentially `fieldsToMark`) and - * position-based highlighting (through providing a `position`). - * - * @param {Object} properties The properties of this Mark. - * - * @param {string?} properties.original The original text that should be marked. - * @param {string?} properties.marked The new text including marks. - * @param {array?} properties.fieldsToMark The array that specifies which text section(s) to mark. - * - * @param {SourceCodeRange?} properties.position The position object: a range in the source code. - * - * @constructor - */ -function Mark( properties ) { - defaults( properties, { original: "", marked: "", fieldsToMark: [] } ); - this._properties = properties; - this.isValid(); -} +const defaultProperties = { original: "", marked: "", fieldsToMark: [] }; /** - * Returns the original text. - * - * @returns {string} The original text. - */ -Mark.prototype.getOriginal = function() { - return this._properties.original; -}; - -/** - * Returns the marked text. - * - * @returns {string} The replaced text. + * Represents a place where highlighting should be applied. */ -Mark.prototype.getMarked = function() { - return this._properties.marked; -}; +class Mark { + /** + * Represents a place where highlighting should be applied. + * We allow both replacement-based highlighting (through providing `original`, `marked`, and potentially `fieldsToMark`) and + * position-based highlighting (through providing a `position`). + * + * @param {Object} [properties] The properties of this Mark. + * + * @param {string?} properties.original The original text that should be marked. + * @param {string?} properties.marked The new text including marks. + * @param {array?} properties.fieldsToMark The array that specifies which text section(s) to mark. + * + * @param {SourceCodeRange?} properties.position The position object: a range in the source code. + */ + constructor( properties ) { + properties = properties || {}; + defaults( properties, defaultProperties ); + this._properties = properties; + this.isValid(); + } -/** - * Returns the fields to mark. - * - * @returns {array} The fields to mark. - */ -Mark.prototype.getFieldsToMark = function() { - return this._properties.fieldsToMark; -}; + /** + * Returns the original text. + * + * @returns {string} The original text. + */ + getOriginal() { + return this._properties.original; + } -/** - * Returns the position information. - * - * @returns {number} The position information. - */ -Mark.prototype.getPosition = function() { - return this._properties.position; -}; + /** + * Returns the marked text. + * + * @returns {string} The replaced text. + */ + getMarked() { + return this._properties.marked; + } -/** - * Returns the start position. - * - * @returns {number} The start position. - */ -Mark.prototype.getPositionStart = function() { - return this._properties.position && this._properties.position.startOffset; -}; + /** + * Returns the fields to mark. + * + * @returns {array} The fields to mark. + */ + getFieldsToMark() { + return this._properties.fieldsToMark; + } -/** - * Returns the end position. - * - * @returns {number} The end position. - */ -Mark.prototype.getPositionEnd = function() { - return this._properties.position && this._properties.position.endOffset; -}; + /** + * Returns the position information. + * + * @returns {number} The position information. + */ + getPosition() { + return this._properties.position; + } -/** - * Sets the start position. - * - * @param {number} positionStart The new start position. - * - * @returns {void} - */ -Mark.prototype.setPositionStart = function( positionStart ) { - this._properties.position.startOffset = positionStart; -}; + /** + * Returns the start position. + * + * @returns {number} The start position. + */ + getPositionStart() { + return this._properties.position && this._properties.position.startOffset; + } -/** - * Sets the end position. - * - * @param {number} positionEnd The new end position. - * - * @returns {void} - */ -Mark.prototype.setPositionEnd = function( positionEnd ) { - this._properties.position.endOffset = positionEnd; -}; + /** + * Returns the end position. + * + * @returns {number} The end position. + */ + getPositionEnd() { + return this._properties.position && this._properties.position.endOffset; + } -/** - * Returns the start position of a block. - * - * @param {number} startOffsetBlock The block start offset. - * - * @returns {number} The start position of a block. - */ -Mark.prototype.setBlockPositionStart = function( startOffsetBlock ) { - this._properties.position.startOffsetBlock = startOffsetBlock; -}; + /** + * Sets the start position. + * + * @param {number} positionStart The new start position. + * + * @returns {void} + */ + setPositionStart( positionStart ) { + this._properties.position.startOffset = positionStart; + } -/** - * Returns the end position of a block. - * - * @param {number} endOffsetBlock The block end offset. - * - * @returns {number} The end position of a block. - */ -Mark.prototype.setBlockPositionEnd = function( endOffsetBlock ) { - this._properties.position.endOffsetBlock = endOffsetBlock; -}; + /** + * Sets the end position. + * + * @param {number} positionEnd The new end position. + * + * @returns {void} + */ + setPositionEnd( positionEnd ) { + this._properties.position.endOffset = positionEnd; + } -/** - * Gets the block client id. - * - * @returns {string} The block client id. - */ -Mark.prototype.getBlockClientId = function() { - return this._properties.position && this._properties.position.clientId; -}; + /** + * Returns the start position of a block. + * + * @param {number} startOffsetBlock The block start offset. + * + * @returns {number} The start position of a block. + */ + setBlockPositionStart( startOffsetBlock ) { + this._properties.position.startOffsetBlock = startOffsetBlock; + } -/** - * Gets the block attribute id. - * - * @returns {string} The block attribute id. - */ -Mark.prototype.getBlockAttributeId = function() { - return this._properties.position && this._properties.position.attributeId; -}; + /** + * Returns the end position of a block. + * + * @param {number} endOffsetBlock The block end offset. + * + * @returns {number} The end position of a block. + */ + setBlockPositionEnd( endOffsetBlock ) { + this._properties.position.endOffsetBlock = endOffsetBlock; + } + /** + * Gets the block client id. + * + * @returns {string} The block client id. + */ + getBlockClientId() { + return this._properties.position && this._properties.position.clientId; + } -/** - * Checks if the mark object is intended for the first section of a Yoast sub-block. - * This method will be used only for Yoast blocks where each block consists of sub-blocks - * with two sections. - * - * @returns {boolean} Whether the mark object is intended for the first section of a Yoast sub-block. - */ -Mark.prototype.isMarkForFirstBlockSection = function() { - return this._properties.position && this._properties.position.isFirstSection; -}; + /** + * Gets the block attribute id. + * + * @returns {string} The block attribute id. + */ + getBlockAttributeId() { + return this._properties.position && this._properties.position.attributeId; + } -/** - * Returns the start position inside block. - * - * @returns {number} The start position inside block if the mark position information, undefined otherwise. - */ -Mark.prototype.getBlockPositionStart = function() { - return this._properties.position && this._properties.position.startOffsetBlock; -}; -/** - * Returns the end position inside block if the mark has position information, undefined otherwise. - * - * @returns {number} The end position inside block. - */ -Mark.prototype.getBlockPositionEnd = function() { - return this._properties.position && this._properties.position.endOffsetBlock; -}; + /** + * Checks if the mark object is intended for the first section of a Yoast sub-block. + * This method will be used only for Yoast blocks where each block consists of sub-blocks + * with two sections. + * + * @returns {boolean} Whether the mark object is intended for the first section of a Yoast sub-block. + */ + isMarkForFirstBlockSection() { + return this._properties.position && this._properties.position.isFirstSection; + } -/** - * Applies this mark to the given text with replacement-based highlighting. - * - * @param {string} text The original text without the mark applied. - * @returns {string} A new text with the mark applied to it. - */ -Mark.prototype.applyWithReplace = function( text ) { - // (=^ ◡ ^=) Cute method to replace everything in a string without using regex. - return text.split( this._properties.original ).join( this._properties.marked ); -}; + /** + * Returns the start position inside block. + * + * @returns {number} The start position inside block if the mark position information, undefined otherwise. + */ + getBlockPositionStart() { + return this._properties.position && this._properties.position.startOffsetBlock; + } -/** - * Applies this mark to the given text with position-based highlighting. - * - * @param {string} text The original text without the mark applied. - * @returns {string} A new text with the mark applied to it. - */ -Mark.prototype.applyWithPosition = function( text ) { - const markStart = ""; - const markEnd = ""; + /** + * Returns the end position inside block if the mark has position information, undefined otherwise. + * + * @returns {number} The end position inside block. + */ + getBlockPositionEnd() { + return this._properties.position && this._properties.position.endOffsetBlock; + } - const newPositionEnd = this.getPositionEnd() + markStart.length; + /** + * Applies this mark to the given text with replacement-based highlighting. + * + * @param {string} text The original text without the mark applied. + * @returns {string} A new text with the mark applied to it. + */ + applyWithReplace( text ) { + // (=^ ◡ ^=) Cute method to replace everything in a string without using regex. + return text.split( this._properties.original ).join( this._properties.marked ); + } - text = text.substring( 0, this.getPositionStart() ) + markStart + text.substring( this.getPositionStart() ); - text = text.substring( 0, newPositionEnd ) + markEnd + text.substring( newPositionEnd ); + /** + * Applies this mark to the given text with position-based highlighting. + * + * @param {string} text The original text without the mark applied. + * @returns {string} A new text with the mark applied to it. + */ + applyWithPosition( text ) { + const markStart = ""; + const markEnd = ""; - return text; -}; + const newPositionEnd = this.getPositionEnd() + markStart.length; -/** - * Serializes the Mark instance to an object. - * - * @returns {Object} The serialized Mark. - */ -Mark.prototype.serialize = function() { - return { - _parseClass: "Mark", - ...this._properties, - }; -}; + text = text.substring( 0, this.getPositionStart() ) + markStart + text.substring( this.getPositionStart() ); + text = text.substring( 0, newPositionEnd ) + markEnd + text.substring( newPositionEnd ); -/** - * Checks if the mark object is valid for position-based highlighting. - * @returns {void} - */ -// eslint-disable-next-line complexity -Mark.prototype.isValid = function() { - if ( ! isUndefined( this.getPositionStart() ) && this.getPositionStart() < 0 ) { - throw new RangeError( "positionStart should be larger or equal than 0." ); + return text; } - if ( ! isUndefined( this.getPositionEnd() ) && this.getPositionEnd() <= 0 ) { - throw new RangeError( "positionEnd should be larger than 0." ); - } - if ( ! isUndefined( this.getPositionStart() ) && ! isUndefined( this.getPositionEnd() ) && - this.getPositionStart() >= this.getPositionEnd() ) { - throw new RangeError( "The positionStart should be smaller than the positionEnd." ); + + /** + * Serializes the Mark instance to an object. + * + * @returns {Object} The serialized Mark. + */ + serialize() { + return { + _parseClass: "Mark", + ...this._properties, + }; } - if ( isUndefined( this.getPositionStart() ) && ! isUndefined( this.getPositionEnd() ) || - isUndefined( this.getPositionEnd() ) && ! isUndefined( this.getPositionStart() ) ) { - throw new Error( "A mark object should either have start and end defined or start and end undefined." ); + + /** + * Checks if the mark object is valid for position-based highlighting. + * @returns {void} + */ + isValid() { + if ( ! isUndefined( this.getPositionStart() ) && this.getPositionStart() < 0 ) { + throw new RangeError( "positionStart should be larger or equal than 0." ); + } + if ( ! isUndefined( this.getPositionEnd() ) && this.getPositionEnd() <= 0 ) { + throw new RangeError( "positionEnd should be larger than 0." ); + } + if ( ! isUndefined( this.getPositionStart() ) && ! isUndefined( this.getPositionEnd() ) && + this.getPositionStart() >= this.getPositionEnd() ) { + throw new RangeError( "The positionStart should be smaller than the positionEnd." ); + } + if ( isUndefined( this.getPositionStart() ) && ! isUndefined( this.getPositionEnd() ) || + isUndefined( this.getPositionEnd() ) && ! isUndefined( this.getPositionStart() ) ) { + throw new Error( "A mark object should either have start and end defined or start and end undefined." ); + } } -}; -/** - * Checks if a mark has position information available. - * @returns {boolean} Returns true if the Mark object has position information, false otherwise. - */ -Mark.prototype.hasPosition = function() { - return ! isUndefined( this.getPositionStart() ); -}; + /** + * Checks if a mark has position information available. + * @returns {boolean} Returns true if the Mark object has position information, false otherwise. + */ + hasPosition() { + return ! isUndefined( this.getPositionStart() ); + } -/** - * Checks if a mark has block position information available. - * A block has position information if the block start offset is available. - * - * @returns {boolean} Returns true if the Mark object has block position information, false otherwise. - */ -Mark.prototype.hasBlockPosition = function() { - return ! isUndefined( this.getBlockPositionStart() ); -}; + /** + * Checks if a mark has block position information available. + * A block has position information if the block start offset is available. + * + * @returns {boolean} Returns true if the Mark object has block position information, false otherwise. + */ + hasBlockPosition() { + return ! isUndefined( this.getBlockPositionStart() ); + } -/** - * Parses the object to a Mark. - * - * @param {Object} serialized The serialized object. - * - * @returns {Mark} The parsed Mark. - */ -Mark.parse = function( serialized ) { - delete serialized._parseClass; - return new Mark( serialized ); -}; + /** + * Parses the object to a Mark. + * + * @param {Object} serialized The serialized object. + * + * @returns {Mark} The parsed Mark. + */ + parse( serialized ) { + delete serialized._parseClass; + return new Mark( serialized ); + } +} export default Mark; diff --git a/packages/yoastseo/src/values/Paper.js b/packages/yoastseo/src/values/Paper.js index 46756c7da71..0a19edaa6a2 100644 --- a/packages/yoastseo/src/values/Paper.js +++ b/packages/yoastseo/src/values/Paper.js @@ -21,338 +21,341 @@ const defaultAttributes = { }; /** - * Constructs the Paper object and sets the keyword property. - * - * @param {string} text The text to use in the analysis. - * @param {object} [attributes] The object containing all attributes. - * @param {string} [attributes.keyword] The main keyword. - * @param {string} [attributes.synonyms] The main keyword's synonyms. - * @param {string} [attributes.description] The SEO description. - * @param {string} [attributes.title] The SEO title. - * @param {number} [attributes.titleWidth] The width of the title in pixels. - * @param {string} [attributes.slug] The slug. - * @param {string} [attributes.locale] The locale. - * @param {string} [attributes.permalink] The base url + slug. - * @param {string} [attributes.date] The date. - * @param {Object} [attributes.wpBlocks] The text, encoded in WordPress block editor blocks. - * @param {Object} [attributes.customData] Custom data. - * @param {string} [attributes.textTitle] The title of the text. - * @param {string} [attributes.writingDirection] The writing direction of the paper. Defaults to left to right (LTR). - * - * @constructor - */ -function Paper( text, attributes ) { - this._text = text || ""; - - this._tree = null; + * Represents an object where the analysis data is stored. + */ +class Paper { + /** + * Constructs the Paper object and sets the keyword property. + * + * @param {string} text The text to use in the analysis. + * @param {object} [attributes] The object containing all attributes. + * @param {string} [attributes.keyword] The main keyword. + * @param {string} [attributes.synonyms] The main keyword's synonyms. + * @param {string} [attributes.description] The SEO description. + * @param {string} [attributes.title] The SEO title. + * @param {number} [attributes.titleWidth] The width of the title in pixels. + * @param {string} [attributes.slug] The slug. + * @param {string} [attributes.locale] The locale. + * @param {string} [attributes.permalink] The base url + slug. + * @param {string} [attributes.date] The date. + * @param {Object} [attributes.wpBlocks] The text, encoded in WordPress block editor blocks. + * @param {Object} [attributes.customData] Custom data. + * @param {string} [attributes.textTitle] The title of the text. + * @param {string} [attributes.writingDirection] The writing direction of the paper. Defaults to left to right (LTR). + */ + constructor( text, attributes ) { + this._text = text || ""; + + this._tree = null; + + attributes = attributes || {}; + defaults( attributes, defaultAttributes ); + + if ( attributes.locale === "" ) { + attributes.locale = defaultAttributes.locale; + } + + if ( attributes.hasOwnProperty( "url" ) ) { + // The 'url' attribute has been deprecated since version 18.8, refer to hasUrl and getUrl below. + console.warn( "The 'url' attribute is deprecated, use 'slug' instead." ); + attributes.slug = attributes.url || attributes.slug; + } + + const onlyLetters = attributes.keyword.replace( /[‘’“”"'.?!:;,¿¡«»&*@#±^%|~`[\](){}⟨⟩<>/\\–\-\u2014\u00d7\u002b\u0026\s]/g, "" ); + + if ( isEmpty( onlyLetters ) ) { + attributes.keyword = defaultAttributes.keyword; + } + + this._attributes = attributes; + } - attributes = attributes || {}; - defaults( attributes, defaultAttributes ); - if ( attributes.locale === "" ) { - attributes.locale = defaultAttributes.locale; + /** + * Checks whether a keyword is available. + * @returns {boolean} Returns true if the Paper has a keyword. + */ + hasKeyword() { + return this._attributes.keyword !== ""; } - if ( attributes.hasOwnProperty( "url" ) ) { - // The 'url' attribute has been deprecated since version 18.8, refer to hasUrl and getUrl below. - console.warn( "The 'url' attribute is deprecated, use 'slug' instead." ); - attributes.slug = attributes.url || attributes.slug; + /** + * Returns the associated keyword or an empty string if no keyword is available. + * @returns {string} Returns Keyword + */ + getKeyword() { + return this._attributes.keyword; } - const onlyLetters = attributes.keyword.replace( /[‘’“”"'.?!:;,¿¡«»&*@#±^%|~`[\](){}⟨⟩<>/\\–\-\u2014\u00d7\u002b\u0026\s]/g, "" ); - - if ( isEmpty( onlyLetters ) ) { - attributes.keyword = defaultAttributes.keyword; + /** + * Checks whether synonyms are available. + * @returns {boolean} Returns true if the Paper has synonyms. + */ + hasSynonyms() { + return this._attributes.synonyms !== ""; } - this._attributes = attributes; -} - - -/** - * Checks whether a keyword is available. - * @returns {boolean} Returns true if the Paper has a keyword. - */ -Paper.prototype.hasKeyword = function() { - return this._attributes.keyword !== ""; -}; - -/** - * Returns the associated keyword or an empty string if no keyword is available. - * @returns {string} Returns Keyword - */ -Paper.prototype.getKeyword = function() { - return this._attributes.keyword; -}; - -/** - * Checks whether synonyms are available. - * @returns {boolean} Returns true if the Paper has synonyms. - */ -Paper.prototype.hasSynonyms = function() { - return this._attributes.synonyms !== ""; -}; - -/** - * Returns the associated synonyms or an empty string if no synonyms is available. - * @returns {string} Returns synonyms. - */ -Paper.prototype.getSynonyms = function() { - return this._attributes.synonyms; -}; - -/** - * Checks whether the text is available. - * @returns {boolean} Returns true if the paper has a text. - */ -Paper.prototype.hasText = function() { - return this._text !== ""; -}; - -/** - * Returns the associated text or am empty string if no text is available. - * @returns {string} Returns text - */ -Paper.prototype.getText = function() { - return this._text; -}; + /** + * Returns the associated synonyms or an empty string if no synonyms is available. + * @returns {string} Returns synonyms. + */ + getSynonyms() { + return this._attributes.synonyms; + } -/** - * Sets the tree. - * - * @param {Node} tree The tree to set. - * - * @returns {void} - */ -Paper.prototype.setTree = function( tree ) { - this._tree = tree; -}; + /** + * Checks whether the text is available. + * @returns {boolean} Returns true if the paper has a text. + */ + hasText() { + return this._text !== ""; + } -/** - * Returns the tree. - * - * @returns {Node} The tree. - */ -Paper.prototype.getTree = function() { - return this._tree; -}; + /** + * Returns the associated text or am empty string if no text is available. + * @returns {string} Returns text + */ + getText() { + return this._text; + } -/** - * Checks whether a description is available. - * @returns {boolean} Returns true if the paper has a description. - */ -Paper.prototype.hasDescription = function() { - return this._attributes.description !== ""; -}; + /** + * Sets the tree. + * + * @param {Node} tree The tree to set. + * + * @returns {void} + */ + setTree( tree ) { + this._tree = tree; + } -/** - * Returns the description or an empty string if no description is available. - * @returns {string} Returns the description. - */ -Paper.prototype.getDescription = function() { - return this._attributes.description; -}; + /** + * Returns the tree. + * + * @returns {Node} The tree. + */ + getTree() { + return this._tree; + } -/** - * Checks whether a title is available - * @returns {boolean} Returns true if the Paper has a title. - */ -Paper.prototype.hasTitle = function() { - return this._attributes.title !== ""; -}; + /** + * Checks whether a description is available. + * @returns {boolean} Returns true if the paper has a description. + */ + hasDescription() { + return this._attributes.description !== ""; + } -/** - * Returns the title, or an empty string of no title is available. - * @returns {string} Returns the title - */ -Paper.prototype.getTitle = function() { - return this._attributes.title; -}; + /** + * Returns the description or an empty string if no description is available. + * @returns {string} Returns the description. + */ + getDescription() { + return this._attributes.description; + } -/** - * Checks whether a title width in pixels is available - * @returns {boolean} Returns true if the Paper has a title. - */ -Paper.prototype.hasTitleWidth = function() { - return this._attributes.titleWidth !== 0; -}; + /** + * Checks whether a title is available + * @returns {boolean} Returns true if the Paper has a title. + */ + hasTitle() { + return this._attributes.title !== ""; + } -/** - * Returns the title width in pixels, or an empty string of no title width in pixels is available. - * @returns {string} Returns the title - */ -Paper.prototype.getTitleWidth = function() { - return this._attributes.titleWidth; -}; + /** + * Returns the title, or an empty string of no title is available. + * @returns {string} Returns the title + */ + getTitle() { + return this._attributes.title; + } -/** - * Checks whether a slug is available - * @returns {boolean} Returns true if the Paper has a slug. - */ -Paper.prototype.hasSlug = function() { - return this._attributes.slug !== ""; -}; + /** + * Checks whether a title width in pixels is available + * @returns {boolean} Returns true if the Paper has a title. + */ + hasTitleWidth() { + return this._attributes.titleWidth !== 0; + } -/** - * Returns the slug, or an empty string of no slug is available. - * @returns {string} Returns the url - */ -Paper.prototype.getSlug = function() { - return this._attributes.slug; -}; + /** + * Returns the title width in pixels, or an empty string of no title width in pixels is available. + * @returns {string} Returns the title + */ + getTitleWidth() { + return this._attributes.titleWidth; + } -/** - * Checks whether an url is available - * @deprecated Since version 18.7. Use hasSlug instead. - * @returns {boolean} Returns true if the Paper has a slug. - */ -Paper.prototype.hasUrl = function() { - console.warn( "This function is deprecated, use hasSlug instead" ); - return this.hasSlug(); -}; + /** + * Checks whether a slug is available + * @returns {boolean} Returns true if the Paper has a slug. + */ + hasSlug() { + return this._attributes.slug !== ""; + } -/** - * Returns the url, or an empty string if no url is available. - * @deprecated Since version 18.8. Use getSlug instead. - * @returns {string} Returns the url - */ -Paper.prototype.getUrl = function() { - console.warn( "This function is deprecated, use getSlug instead" ); - return this.getSlug(); -}; + /** + * Returns the slug, or an empty string of no slug is available. + * @returns {string} Returns the url + */ + getSlug() { + return this._attributes.slug; + } -/** - * Checks whether a locale is available - * @returns {boolean} Returns true if the paper has a locale - */ -Paper.prototype.hasLocale = function() { - return this._attributes.locale !== ""; -}; + /** + * Checks whether an url is available + * @deprecated Since version 18.7. Use hasSlug instead. + * @returns {boolean} Returns true if the Paper has a slug. + */ + hasUrl() { + console.warn( "This function is deprecated, use hasSlug instead" ); + return this.hasSlug(); + } -/** - * Returns the locale or an empty string if no locale is available - * @returns {string} Returns the locale - */ -Paper.prototype.getLocale = function() { - return this._attributes.locale; -}; + /** + * Returns the url, or an empty string if no url is available. + * @deprecated Since version 18.8. Use getSlug instead. + * @returns {string} Returns the url + */ + getUrl() { + console.warn( "This function is deprecated, use getSlug instead" ); + return this.getSlug(); + } -/** - * Gets the information of the writing direction of the paper. - * It returns "LTR" (left to right) if this attribute is not provided. - * - * @returns {string} Returns the information of the writing direction of the paper. - */ -Paper.prototype.getWritingDirection = function() { - return this._attributes.writingDirection; -}; + /** + * Checks whether a locale is available + * @returns {boolean} Returns true if the paper has a locale + */ + hasLocale() { + return this._attributes.locale !== ""; + } -/** - * Checks whether a permalink is available - * @returns {boolean} Returns true if the Paper has a permalink. - */ -Paper.prototype.hasPermalink = function() { - return this._attributes.permalink !== ""; -}; + /** + * Returns the locale or an empty string if no locale is available + * @returns {string} Returns the locale + */ + getLocale() { + return this._attributes.locale; + } -/** - * Returns the permalink, or an empty string if no permalink is available. - * @returns {string} Returns the permalink. - */ -Paper.prototype.getPermalink = function() { - return this._attributes.permalink; -}; + /** + * Gets the information of the writing direction of the paper. + * It returns "LTR" (left to right) if this attribute is not provided. + * + * @returns {string} Returns the information of the writing direction of the paper. + */ + getWritingDirection() { + return this._attributes.writingDirection; + } -/** - * Checks whether a date is available. - * @returns {boolean} Returns true if the Paper has a date. - */ -Paper.prototype.hasDate = function() { - return this._attributes.date !== ""; -}; + /** + * Checks whether a permalink is available + * @returns {boolean} Returns true if the Paper has a permalink. + */ + hasPermalink() { + return this._attributes.permalink !== ""; + } -/** - * Returns the date, or an empty string if no date is available. - * @returns {string} Returns the date. - */ -Paper.prototype.getDate = function() { - return this._attributes.date; -}; + /** + * Returns the permalink, or an empty string if no permalink is available. + * @returns {string} Returns the permalink. + */ + getPermalink() { + return this._attributes.permalink; + } -/** - * Checks whether custom data is available. - * @returns {boolean} Returns true if the Paper has custom data. - */ -Paper.prototype.hasCustomData = function() { - return ! isEmpty( this._attributes.customData ); -}; + /** + * Checks whether a date is available. + * @returns {boolean} Returns true if the Paper has a date. + */ + hasDate() { + return this._attributes.date !== ""; + } -/** - * Returns the custom data, or an empty object if no data is available. - * @returns {Object} Returns the custom data. - */ -Paper.prototype.getCustomData = function() { - return this._attributes.customData; -}; + /** + * Returns the date, or an empty string if no date is available. + * @returns {string} Returns the date. + */ + getDate() { + return this._attributes.date; + } -/** - * Checks whether a text title is available. - * @returns {boolean} Returns true if the Paper has a text title. - */ -Paper.prototype.hasTextTitle = function() { - return this._attributes.textTitle !== "" && ! isNil( this._attributes.textTitle ); -}; + /** + * Checks whether custom data is available. + * @returns {boolean} Returns true if the Paper has custom data. + */ + hasCustomData() { + return ! isEmpty( this._attributes.customData ); + } -/** - * Returns the text title, or an empty string if no data is available. - * @returns {string} Returns the text title. - */ -Paper.prototype.getTextTitle = function() { - return this._attributes.textTitle; -}; + /** + * Returns the custom data, or an empty object if no data is available. + * @returns {Object} Returns the custom data. + */ + getCustomData() { + return this._attributes.customData; + } -/** - * Serializes the Paper instance to an object. - * - * @returns {Object} The serialized Paper. - */ -Paper.prototype.serialize = function() { - return { - _parseClass: "Paper", - text: this._text, - ...this._attributes, - }; -}; + /** + * Checks whether a text title is available. + * @returns {boolean} Returns true if the Paper has a text title. + */ + hasTextTitle() { + return this._attributes.textTitle !== "" && ! isNil( this._attributes.textTitle ); + } -/** - * Checks whether the given paper has the same properties as this instance. - * - * @param {Paper} paper The paper to compare to. - * - * @returns {boolean} Whether the given paper is identical or not. - */ -Paper.prototype.equals = function( paper ) { - return this._text === paper.getText() && isEqual( this._attributes, paper._attributes ); -}; + /** + * Returns the text title, or an empty string if no data is available. + * @returns {string} Returns the text title. + */ + getTextTitle() { + return this._attributes.textTitle; + } -/** - * Parses the object to a Paper. - * - * @param {Object|Paper} serialized The serialized object or Paper instance. - * - * @returns {Paper} The parsed Paper. - */ -Paper.parse = function( serialized ) { - // For ease of use, check if it is not already a Paper instance. - if ( serialized instanceof Paper ) { - return serialized; + /** + * Serializes the Paper instance to an object. + * + * @returns {Object} The serialized Paper. + */ + serialize() { + return { + _parseClass: "Paper", + text: this._text, + ...this._attributes, + }; } - // _parseClass is taken here, so it doesn't end up in the attributes. - // eslint-disable-next-line no-unused-vars - const { text, _parseClass, ...attributes } = serialized; + /** + * Checks whether the given paper has the same properties as this instance. + * + * @param {Paper} paper The paper to compare to. + * + * @returns {boolean} Whether the given paper is identical or not. + */ + equals( paper ) { + return this._text === paper.getText() && isEqual( this._attributes, paper._attributes ); + } - return new Paper( text, attributes ); -}; + /** + * Parses the object to a Paper. + * + * @param {Object|Paper} serialized The serialized object or Paper instance. + * + * @returns {Paper} The parsed Paper. + */ + parse( serialized ) { + // For ease of use, check if it is not already a Paper instance. + if ( serialized instanceof Paper ) { + return serialized; + } + + // _parseClass is taken here, so it doesn't end up in the attributes. + // eslint-disable-next-line no-unused-vars + const { text, _parseClass, ...attributes } = serialized; + + return new Paper( text, attributes ); + } +} export default Paper; diff --git a/packages/yoastseo/src/worker/AnalysisWebWorker.js b/packages/yoastseo/src/worker/AnalysisWebWorker.js index 4ae39dc7cdb..8a00bbc7a96 100644 --- a/packages/yoastseo/src/worker/AnalysisWebWorker.js +++ b/packages/yoastseo/src/worker/AnalysisWebWorker.js @@ -1229,7 +1229,7 @@ export default class AnalysisWebWorker { return await Promise.all( keywordKeys.map( key => { this._relatedKeywords[ key ] = relatedKeywords[ key ]; - const relatedPaper = Paper.parse( { + const relatedPaper = Paper.prototype.parse( { ...paper.serialize(), keyword: this._relatedKeywords[ key ].keyword, synonyms: this._relatedKeywords[ key ].synonyms, diff --git a/packages/yoastseo/src/worker/transporter/parse.js b/packages/yoastseo/src/worker/transporter/parse.js index 76a63a79c1f..6f8ce291820 100644 --- a/packages/yoastseo/src/worker/transporter/parse.js +++ b/packages/yoastseo/src/worker/transporter/parse.js @@ -31,9 +31,7 @@ export default function parse( thing ) { const thingIsObject = isObject( thing ); if ( thingIsObject && thing._parseClass && PARSE_CLASSES[ thing._parseClass ] ) { - return thing._parseClass === "Sentence" || thing._parseClass === "Clause" - ? PARSE_CLASSES[ thing._parseClass ].prototype.parse( thing ) - : PARSE_CLASSES[ thing._parseClass ].parse( thing ); + return PARSE_CLASSES[ thing._parseClass ].prototype.parse( thing ); } if ( thingIsObject ) { From 781a62fdfdd6873de1160f010ef2e56ee0d8cdcb Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Thu, 11 Jan 2024 13:50:05 +0100 Subject: [PATCH 004/313] use `import` statement instead of `require` --- packages/yoastseo/src/scoring/contentAssessor.js | 4 +++- packages/yoastseo/src/scoring/cornerstone/contentAssessor.js | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/yoastseo/src/scoring/contentAssessor.js b/packages/yoastseo/src/scoring/contentAssessor.js index f5a161c171f..3c93b86266d 100644 --- a/packages/yoastseo/src/scoring/contentAssessor.js +++ b/packages/yoastseo/src/scoring/contentAssessor.js @@ -1,3 +1,5 @@ +import { inherits } from "util"; + import Assessor from "./assessor.js"; import ParagraphTooLong from "./assessments/readability/ParagraphTooLongAssessment.js"; import SentenceLengthInText from "./assessments/readability/SentenceLengthInTextAssessment.js"; @@ -40,7 +42,7 @@ const ContentAssessor = function( researcher, options = {} ) { ]; }; -require( "util" ).inherits( ContentAssessor, Assessor ); +inherits( ContentAssessor, Assessor ); /** * Calculates the weighted rating for languages that have all assessments based on a given rating. diff --git a/packages/yoastseo/src/scoring/cornerstone/contentAssessor.js b/packages/yoastseo/src/scoring/cornerstone/contentAssessor.js index cb920a0f9c3..c090ecb05d3 100644 --- a/packages/yoastseo/src/scoring/cornerstone/contentAssessor.js +++ b/packages/yoastseo/src/scoring/cornerstone/contentAssessor.js @@ -1,3 +1,4 @@ +import { inherits } from "util"; import Assessor from "../assessor.js"; import ContentAssessor from "../contentAssessor"; import ParagraphTooLong from "../assessments/readability/ParagraphTooLongAssessment.js"; @@ -50,7 +51,7 @@ const CornerStoneContentAssessor = function( researcher, options = {} ) { ]; }; -require( "util" ).inherits( CornerStoneContentAssessor, ContentAssessor ); +inherits( CornerStoneContentAssessor, ContentAssessor ); export default CornerStoneContentAssessor; From e63d1d727a397e6dc0089493fac3c80a6457522c Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Mon, 15 Jan 2024 11:40:34 +0100 Subject: [PATCH 005/313] Refactor the class --- .../scoring/renderers/AssessorPresenter.js | 633 +++++++++--------- 1 file changed, 320 insertions(+), 313 deletions(-) diff --git a/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js b/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js index 00a64bac13a..c79652732d4 100644 --- a/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js +++ b/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js @@ -1,372 +1,379 @@ +/* eslint-disable tree-shaking/no-side-effects-in-initialization */ import { __ } from "@wordpress/i18n"; import { difference, forEach, isNumber, isObject, isUndefined } from "lodash-es"; import { assessmentPresenterResult as template } from "../../snippetPreview/templates.js"; import scoreToRating from "../interpreters/scoreToRating.js"; import createConfig from "../../config/presenter.js"; -/** - * Constructs the AssessorPresenter. - * - * @param {Object} args A list of arguments to use in the presenter. - * @param {object} args.targets The HTML elements to render the output to. - * @param {string} args.targets.output The HTML element to render the individual ratings out to. - * @param {string} args.targets.overall The HTML element to render the overall rating out to. - * @param {string} args.keyword The keyword to use for checking, when calculating the overall rating. - * @param {SEOAssessor} args.assessor The Assessor object to retrieve assessment results from. - * - * @constructor - */ -var AssessorPresenter = function( args ) { - this.keyword = args.keyword; - this.assessor = args.assessor; - this.output = args.targets.output; - this.overall = args.targets.overall || "overallScore"; - this.presenterConfig = createConfig(); - - this._disableMarkerButtons = false; - - this._activeMarker = false; -}; /** - * Sets the keyword. - * - * @param {string} keyword The keyword to use. - * @returns {void} + * Represents the AssessorPresenter. */ -AssessorPresenter.prototype.setKeyword = function( keyword ) { - this.keyword = keyword; -}; +class AssessorPresenter { + /** + * Constructs the AssessorPresenter. + * + * @param {Object} args A list of arguments to use in the presenter. + * @param {object} args.targets The HTML elements to render the output to. + * @param {string} args.targets.output The HTML element to render the individual ratings out to. + * @param {string} args.targets.overall The HTML element to render the overall rating out to. + * @param {string} args.keyword The keyword to use for checking, when calculating the overall rating. + * @param {SEOAssessor} args.assessor The Assessor object to retrieve assessment results from. + * + * @constructor + */ + construct( args ) { + this.keyword = args.keyword; + this.assessor = args.assessor; + this.output = args.targets.output; + this.overall = args.targets.overall || "overallScore"; + this.presenterConfig = createConfig(); + + this._disableMarkerButtons = false; -/** - * Checks whether or not a specific property exists in the presenter configuration. - * - * @param {string} property The property name to search for. - * @returns {boolean} Whether or not the property exists. - */ -AssessorPresenter.prototype.configHasProperty = function( property ) { - return this.presenterConfig.hasOwnProperty( property ); -}; - -/** - * Gets a fully formatted indicator object that can be used. - * - * @param {string} rating The rating to use. - * @returns {Object} An object containing the class, the screen reader text, and the full text. - */ -AssessorPresenter.prototype.getIndicator = function( rating ) { - return { - className: this.getIndicatorColorClass( rating ), - screenReaderText: this.getIndicatorScreenReaderText( rating ), - fullText: this.getIndicatorFullText( rating ), - screenReaderReadabilityText: this.getIndicatorScreenReaderReadabilityText( rating ), - }; -}; + this._activeMarker = false; + } -/** - * Gets the indicator color class from the presenter configuration, if it exists. - * - * @param {string} rating The rating to check against the config. - * @returns {string} String containing the CSS class to be used. - */ -AssessorPresenter.prototype.getIndicatorColorClass = function( rating ) { - if ( ! this.configHasProperty( rating ) ) { - return ""; + /** + * Sets the keyword. + * + * @param {string} keyword The keyword to use. + * @returns {void} + */ + setKeyword( keyword ) { + this.keyword = keyword; } - return this.presenterConfig[ rating ].className; -}; + /** + * Checks whether a specific property exists in the presenter configuration. + * + * @param {string} property The property name to search for. + * @returns {boolean} Whether or not the property exists. + */ + configHasProperty( property ) { + return this.presenterConfig.hasOwnProperty( property ); + } -/** - * Get the indicator screen reader text from the presenter configuration, if it exists. - * - * @param {string} rating The rating to check against the config. - * @returns {string} Translated string containing the screen reader text to be used. - */ -AssessorPresenter.prototype.getIndicatorScreenReaderText = function( rating ) { - if ( ! this.configHasProperty( rating ) ) { - return ""; + /** + * Gets a fully formatted indicator object that can be used. + * + * @param {string} rating The rating to use. + * @returns {Object} An object containing the class, the screen reader text, and the full text. + */ + getIndicator( rating ) { + return { + className: this.getIndicatorColorClass( rating ), + screenReaderText: this.getIndicatorScreenReaderText( rating ), + fullText: this.getIndicatorFullText( rating ), + screenReaderReadabilityText: this.getIndicatorScreenReaderReadabilityText( rating ), + }; } - return this.presenterConfig[ rating ].screenReaderText; -}; + /** + * Gets the indicator color class from the presenter configuration, if it exists. + * + * @param {string} rating The rating to check against the config. + * @returns {string} String containing the CSS class to be used. + */ + getIndicatorColorClass( rating ) { + if ( ! this.configHasProperty( rating ) ) { + return ""; + } -/** - * Get the indicator screen reader readability text from the presenter configuration, if it exists. - * - * @param {string} rating The rating to check against the config. - * @returns {string} Translated string containing the screen reader readability text to be used. - */ -AssessorPresenter.prototype.getIndicatorScreenReaderReadabilityText = function( rating ) { - if ( ! this.configHasProperty( rating ) ) { - return ""; + return this.presenterConfig[ rating ].className; } - return this.presenterConfig[ rating ].screenReaderReadabilityText; -}; + /** + * Get the indicator screen reader text from the presenter configuration, if it exists. + * + * @param {string} rating The rating to check against the config. + * @returns {string} Translated string containing the screen reader text to be used. + */ + getIndicatorScreenReaderText( rating ) { + if ( ! this.configHasProperty( rating ) ) { + return ""; + } -/** - * Get the indicator full text from the presenter configuration, if it exists. - * - * @param {string} rating The rating to check against the config. - * @returns {string} Translated string containing the full text to be used. - */ -AssessorPresenter.prototype.getIndicatorFullText = function( rating ) { - if ( ! this.configHasProperty( rating ) ) { - return ""; + return this.presenterConfig[ rating ].screenReaderText; } - return this.presenterConfig[ rating ].fullText; -}; + /** + * Get the indicator screen reader readability text from the presenter configuration, if it exists. + * + * @param {string} rating The rating to check against the config. + * @returns {string} Translated string containing the screen reader readability text to be used. + */ + getIndicatorScreenReaderReadabilityText( rating ) { + if ( ! this.configHasProperty( rating ) ) { + return ""; + } -/** - * Adds a rating based on the numeric score. - * - * @param {Object} result Object based on the Assessment result. Requires a score property to work. - * @returns {Object} The Assessment result object with the rating added. - */ -AssessorPresenter.prototype.resultToRating = function( result ) { - if ( ! isObject( result ) ) { - return ""; + return this.presenterConfig[ rating ].screenReaderReadabilityText; } - result.rating = scoreToRating( result.score ); - - return result; -}; + /** + * Get the indicator full text from the presenter configuration, if it exists. + * + * @param {string} rating The rating to check against the config. + * @returns {string} Translated string containing the full text to be used. + */ + getIndicatorFullText( rating ) { + if ( ! this.configHasProperty( rating ) ) { + return ""; + } -/** - * Takes the individual assessment results, sorts and rates them. - * - * @returns {Object} Object containing all the individual ratings. - */ -AssessorPresenter.prototype.getIndividualRatings = function() { - var ratings = {}; - var validResults = this.sort( this.assessor.getValidResults() ); - var mappedResults = validResults.map( this.resultToRating ); + return this.presenterConfig[ rating ].fullText; + } - forEach( mappedResults, function( item, key ) { - ratings[ key ] = this.addRating( item ); - }.bind( this ) ); + /** + * Adds a rating based on the numeric score. + * + * @param {Object} result Object based on the Assessment result. Requires a score property to work. + * @returns {Object} The Assessment result object with the rating added. + */ + resultToRating( result ) { + if ( ! isObject( result ) ) { + return ""; + } - return ratings; -}; + result.rating = scoreToRating( result.score ); -/** - * Excludes items from the results that are present in the exclude array. - * - * @param {Array} results Array containing the items to filter through. - * @param {Array} exclude Array of results to exclude. - * @returns {Array} Array containing items that remain after exclusion. - */ -AssessorPresenter.prototype.excludeFromResults = function( results, exclude ) { - return difference( results, exclude ); -}; + return result; + } -/** - * Sorts results based on their score property and always places items considered to be unsortable, at the top. - * - * @param {Array} results Array containing the results that need to be sorted. - * @returns {Array} Array containing the sorted results. - */ -AssessorPresenter.prototype.sort = function( results ) { - var unsortables = this.getUndefinedScores( results ); - var sortables = this.excludeFromResults( results, unsortables ); + /** + * Takes the individual assessment results, sorts and rates them. + * + * @returns {Object} Object containing all the individual ratings. + */ + getIndividualRatings() { + const ratings = {}; + const validResults = this.sort( this.assessor.getValidResults() ); + const mappedResults = validResults.map( this.resultToRating ); + + forEach( mappedResults, function( item, key ) { + ratings[ key ] = this.addRating( item ); + }.bind( this ) ); + + return ratings; + } - sortables.sort( function( a, b ) { - return a.score - b.score; - } ); + /** + * Excludes items from the results that are present in the exclude array. + * + * @param {Array} results Array containing the items to filter through. + * @param {Array} exclude Array of results to exclude. + * @returns {Array} Array containing items that remain after exclusion. + */ + excludeFromResults( results, exclude ) { + return difference( results, exclude ); + } - return unsortables.concat( sortables ); -}; + /** + * Sorts results based on their score property and always places items considered to be unsortable, at the top. + * + * @param {Array} results Array containing the results that need to be sorted. + * @returns {Array} Array containing the sorted results. + */ + sort( results ) { + const unsortables = this.getUndefinedScores( results ); + const sortables = this.excludeFromResults( results, unsortables ); + + sortables.sort( function( a, b ) { + return a.score - b.score; + } ); + + return unsortables.concat( sortables ); + } -/** - * Returns a subset of results that have an undefined score or a score set to zero. - * - * @param {Array} results The results to filter through. - * @returns {Array} A subset of results containing items with an undefined score or where the score is zero. - */ -AssessorPresenter.prototype.getUndefinedScores = function( results ) { - return results.filter( function( result ) { - return isUndefined( result.score ) || result.score === 0; - } ); -}; + /** + * Returns a subset of results that have an undefined score or a score set to zero. + * + * @param {Array} results The results to filter through. + * @returns {Array} A subset of results containing items with an undefined score or where the score is zero. + */ + getUndefinedScores( results ) { + return results.filter( function( result ) { + return isUndefined( result.score ) || result.score === 0; + } ); + } -/** - * Creates a rating object based on the item that is being passed. - * - * @param {AssessmentResult} item The item to check and create a rating object from. - * @returns {Object} Object containing a parsed item, including a colored indicator. - */ -AssessorPresenter.prototype.addRating = function( item ) { - var indicator = this.getIndicator( item.rating ); - indicator.text = item.text; - indicator.identifier = item.getIdentifier(); + /** + * Creates a rating object based on the item that is being passed. + * + * @param {AssessmentResult} item The item to check and create a rating object from. + * @returns {Object} Object containing a parsed item, including a colored indicator. + */ + addRating( item ) { + const indicator = this.getIndicator( item.rating ); + indicator.text = item.text; + indicator.identifier = item.getIdentifier(); + + if ( item.hasMarker() ) { + indicator.marker = item.getMarker(); + } - if ( item.hasMarker() ) { - indicator.marker = item.getMarker(); + return indicator; } - return indicator; -}; + /** + * Calculates the overall rating score based on the overall score. + * + * @param {Number} overallScore The overall score to use in the calculation. + * @returns {Object} The rating based on the score. + */ + getOverallRating( overallScore ) { + let rating = 0; + + if ( this.keyword === "" ) { + return this.resultToRating( { score: rating } ); + } -/** - * Calculates the overall rating score based on the overall score. - * - * @param {Number} overallScore The overall score to use in the calculation. - * @returns {Object} The rating based on the score. - */ -AssessorPresenter.prototype.getOverallRating = function( overallScore ) { - var rating = 0; + if ( isNumber( overallScore ) ) { + rating = ( overallScore / 10 ); + } - if ( this.keyword === "" ) { return this.resultToRating( { score: rating } ); } - if ( isNumber( overallScore ) ) { - rating = ( overallScore / 10 ); - } + /** + * Mark with a given marker. This will set the active marker to the correct value. + * + * @param {string} identifier The identifier for the assessment/marker. + * @param {Function} marker The marker function. + * @returns {void} + */ + markAssessment( identifier, marker ) { + if ( this._activeMarker === identifier ) { + this.removeAllMarks(); + this._activeMarker = false; + } else { + marker(); + this._activeMarker = identifier; + } - return this.resultToRating( { score: rating } ); -}; + this.render(); + } -/** - * Mark with a given marker. This will set the active marker to the correct value. - * - * @param {string} identifier The identifier for the assessment/marker. - * @param {Function} marker The marker function. - * @returns {void} - */ -AssessorPresenter.prototype.markAssessment = function( identifier, marker ) { - if ( this._activeMarker === identifier ) { - this.removeAllMarks(); + /** + * Disables the currently active marker in the UI. + * + * @returns {void} + */ + disableMarker() { this._activeMarker = false; - } else { - marker(); - this._activeMarker = identifier; + this.render(); } - this.render(); -}; - -/** - * Disables the currently active marker in the UI. - * - * @returns {void} - */ -AssessorPresenter.prototype.disableMarker = function() { - this._activeMarker = false; - this.render(); -}; - -/** - * Disables the marker buttons. - * - * @returns {void} - */ -AssessorPresenter.prototype.disableMarkerButtons = function() { - this._disableMarkerButtons = true; - this.render(); -}; - -/** - * Enables the marker buttons. - * - * @returns {void} - */ -AssessorPresenter.prototype.enableMarkerButtons = function() { - this._disableMarkerButtons = false; - this.render(); -}; + /** + * Disables the marker buttons. + * + * @returns {void} + */ + disableMarkerButtons() { + this._disableMarkerButtons = true; + this.render(); + } -/** - * Adds an event listener for the marker button - * - * @param {string} identifier The identifier for the assessment the marker belongs to. - * @param {Function} marker The marker function that can mark the assessment in the text. - * @returns {void} - */ -AssessorPresenter.prototype.addMarkerEventHandler = function( identifier, marker ) { - var container = document.getElementById( this.output ); - var markButton = container.getElementsByClassName( "js-assessment-results__mark-" + identifier )[ 0 ]; + /** + * Enables the marker buttons. + * + * @returns {void} + */ + enableMarkerButtons() { + this._disableMarkerButtons = false; + this.render(); + } - markButton.addEventListener( "click", this.markAssessment.bind( this, identifier, marker ) ); -}; + /** + * Adds an event listener for the marker button + * + * @param {string} identifier The identifier for the assessment the marker belongs to. + * @param {Function} marker The marker function that can mark the assessment in the text. + * @returns {void} + */ + addMarkerEventHandler( identifier, marker ) { + const container = document.getElementById( this.output ); + const markButton = container.getElementsByClassName( "js-assessment-results__mark-" + identifier )[ 0 ]; + + markButton.addEventListener( "click", this.markAssessment.bind( this, identifier, marker ) ); + } -/** - * Renders out both the individual and the overall ratings. - * - * @returns {void} - */ -AssessorPresenter.prototype.render = function() { - this.renderIndividualRatings(); - this.renderOverallRating(); -}; + /** + * Renders out both the individual and the overall ratings. + * + * @returns {void} + */ + render() { + this.renderIndividualRatings(); + this.renderOverallRating(); + } -/** - * Adds event handlers to the mark buttons - * - * @param {Array} scores The list of rendered scores. - * - * @returns {void} - */ -AssessorPresenter.prototype.bindMarkButtons = function( scores ) { - // Make sure the button works for every score with a marker. - forEach( scores, function( score ) { - if ( score.hasOwnProperty( "marker" ) ) { - this.addMarkerEventHandler( score.identifier, score.marker ); - } - }.bind( this ) ); -}; + /** + * Adds event handlers to the mark buttons + * + * @param {Array} scores The list of rendered scores. + * + * @returns {void} + */ + bindMarkButtons( scores ) { + // Make sure the button works for every score with a marker. + forEach( scores, function( score ) { + if ( score.hasOwnProperty( "marker" ) ) { + this.addMarkerEventHandler( score.identifier, score.marker ); + } + }.bind( this ) ); + } -/** - * Removes all marks currently on the text - * - * @returns {void} - */ -AssessorPresenter.prototype.removeAllMarks = function() { - var marker = this.assessor.getSpecificMarker(); + /** + * Removes all marks currently on the text + * + * @returns {void} + */ + removeAllMarks() { + const marker = this.assessor.getSpecificMarker(); - marker( this.assessor.getPaper(), [] ); -}; + marker( this.assessor.getPaper(), [] ); + } -/** - * Renders out the individual ratings. - * - * @returns {void} - */ -AssessorPresenter.prototype.renderIndividualRatings = function() { - var outputTarget = document.getElementById( this.output ); - var scores = this.getIndividualRatings(); - - outputTarget.innerHTML = template( { - scores: scores, - i18n: { - disabledMarkText: __( "Marks are disabled in current view", "wordpress-seo" ), - markInText: __( "Mark this result in the text", "wordpress-seo" ), - removeMarksInText: __( "Remove marks in the text", "wordpress-seo" ), - }, - activeMarker: this._activeMarker, - markerButtonsDisabled: this._disableMarkerButtons, - } ); - - this.bindMarkButtons( scores ); -}; + /** + * Renders out the individual ratings. + * + * @returns {void} + */ + renderIndividualRatings() { + const outputTarget = document.getElementById( this.output ); + const scores = this.getIndividualRatings(); + + outputTarget.innerHTML = template( { + scores: scores, + i18n: { + disabledMarkText: __( "Marks are disabled in current view", "wordpress-seo" ), + markInText: __( "Mark this result in the text", "wordpress-seo" ), + removeMarksInText: __( "Remove marks in the text", "wordpress-seo" ), + }, + activeMarker: this._activeMarker, + markerButtonsDisabled: this._disableMarkerButtons, + } ); + + this.bindMarkButtons( scores ); + } -/** - * Renders out the overall rating. - * - * @returns {void} - */ -AssessorPresenter.prototype.renderOverallRating = function() { - var overallRating = this.getOverallRating( this.assessor.calculateOverallScore() ); - var overallRatingElement = document.getElementById( this.overall ); + /** + * Renders out the overall rating. + * + * @returns {void} + */ + renderOverallRating() { + const overallRating = this.getOverallRating( this.assessor.calculateOverallScore() ); + const overallRatingElement = document.getElementById( this.overall ); + + if ( ! overallRatingElement ) { + return; + } - if ( ! overallRatingElement ) { - return; + overallRatingElement.className = "overallScore " + this.getIndicatorColorClass( overallRating.rating ); } - - overallRatingElement.className = "overallScore " + this.getIndicatorColorClass( overallRating.rating ); -}; +} export default AssessorPresenter; From 386cc38ccf9ae2289043aaff64079585540f8674 Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Tue, 16 Jan 2024 16:15:39 +0100 Subject: [PATCH 006/313] Explicitly make parse method static. This way this method can only be accessed from the Class itself and not from the object instance of the class. --- packages/yoastseo/src/languageProcessing/values/Clause.js | 2 +- .../yoastseo/src/languageProcessing/values/ProminentWord.js | 2 +- packages/yoastseo/src/languageProcessing/values/Sentence.js | 2 +- packages/yoastseo/src/values/AssessmentResult.js | 2 +- packages/yoastseo/src/values/Mark.js | 2 +- packages/yoastseo/src/values/Paper.js | 2 +- packages/yoastseo/src/worker/AnalysisWebWorker.js | 2 +- packages/yoastseo/src/worker/transporter/parse.js | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/yoastseo/src/languageProcessing/values/Clause.js b/packages/yoastseo/src/languageProcessing/values/Clause.js index b25fbc4ba90..42510f37ade 100644 --- a/packages/yoastseo/src/languageProcessing/values/Clause.js +++ b/packages/yoastseo/src/languageProcessing/values/Clause.js @@ -97,7 +97,7 @@ class Clause { * * @returns {Clause} The parsed Clause. */ - parse( serialized ) { + static parse( serialized ) { const clause = new Clause( serialized.clauseText, serialized.auxiliaries ); clause.setPassive( serialized.isPassive ); diff --git a/packages/yoastseo/src/languageProcessing/values/ProminentWord.js b/packages/yoastseo/src/languageProcessing/values/ProminentWord.js index d190083b187..fc3cb373129 100644 --- a/packages/yoastseo/src/languageProcessing/values/ProminentWord.js +++ b/packages/yoastseo/src/languageProcessing/values/ProminentWord.js @@ -87,7 +87,7 @@ class ProminentWord { * * @returns {ProminentWord} The parsed ProminentWord. */ - parse( serialized ) { + static parse( serialized ) { return new ProminentWord( serialized.word, serialized.stem, serialized.occurrences ); } } diff --git a/packages/yoastseo/src/languageProcessing/values/Sentence.js b/packages/yoastseo/src/languageProcessing/values/Sentence.js index af164af4399..948fc5354f7 100644 --- a/packages/yoastseo/src/languageProcessing/values/Sentence.js +++ b/packages/yoastseo/src/languageProcessing/values/Sentence.js @@ -99,7 +99,7 @@ class Sentence { * * @returns {Sentence} The parsed Sentence. */ - parse( serialized ) { + static parse( serialized ) { const sentence = new Sentence( serialized.sentenceText ); sentence.setClauses( serialized.clauses ); sentence.setPassive( serialized.isPassive ); diff --git a/packages/yoastseo/src/values/AssessmentResult.js b/packages/yoastseo/src/values/AssessmentResult.js index 489ea640a8d..a8ab557ad31 100644 --- a/packages/yoastseo/src/values/AssessmentResult.js +++ b/packages/yoastseo/src/values/AssessmentResult.js @@ -299,7 +299,7 @@ class AssessmentResult { * * @returns {AssessmentResult} The parsed AssessmentResult. */ - parse( serialized ) { + static parse( serialized ) { const result = new AssessmentResult( { text: serialized.text, score: serialized.score, diff --git a/packages/yoastseo/src/values/Mark.js b/packages/yoastseo/src/values/Mark.js index 7b87570a384..1f5383f52f9 100644 --- a/packages/yoastseo/src/values/Mark.js +++ b/packages/yoastseo/src/values/Mark.js @@ -260,7 +260,7 @@ class Mark { * * @returns {Mark} The parsed Mark. */ - parse( serialized ) { + static parse( serialized ) { delete serialized._parseClass; return new Mark( serialized ); } diff --git a/packages/yoastseo/src/values/Paper.js b/packages/yoastseo/src/values/Paper.js index 0a19edaa6a2..e9e385f8800 100644 --- a/packages/yoastseo/src/values/Paper.js +++ b/packages/yoastseo/src/values/Paper.js @@ -344,7 +344,7 @@ class Paper { * * @returns {Paper} The parsed Paper. */ - parse( serialized ) { + static parse( serialized ) { // For ease of use, check if it is not already a Paper instance. if ( serialized instanceof Paper ) { return serialized; diff --git a/packages/yoastseo/src/worker/AnalysisWebWorker.js b/packages/yoastseo/src/worker/AnalysisWebWorker.js index 8a00bbc7a96..4ae39dc7cdb 100644 --- a/packages/yoastseo/src/worker/AnalysisWebWorker.js +++ b/packages/yoastseo/src/worker/AnalysisWebWorker.js @@ -1229,7 +1229,7 @@ export default class AnalysisWebWorker { return await Promise.all( keywordKeys.map( key => { this._relatedKeywords[ key ] = relatedKeywords[ key ]; - const relatedPaper = Paper.prototype.parse( { + const relatedPaper = Paper.parse( { ...paper.serialize(), keyword: this._relatedKeywords[ key ].keyword, synonyms: this._relatedKeywords[ key ].synonyms, diff --git a/packages/yoastseo/src/worker/transporter/parse.js b/packages/yoastseo/src/worker/transporter/parse.js index 6f8ce291820..e779ac8d795 100644 --- a/packages/yoastseo/src/worker/transporter/parse.js +++ b/packages/yoastseo/src/worker/transporter/parse.js @@ -31,7 +31,7 @@ export default function parse( thing ) { const thingIsObject = isObject( thing ); if ( thingIsObject && thing._parseClass && PARSE_CLASSES[ thing._parseClass ] ) { - return PARSE_CLASSES[ thing._parseClass ].prototype.parse( thing ); + return PARSE_CLASSES[ thing._parseClass ].parse( thing ); } if ( thingIsObject ) { From 4215e2c63ebb65f4700e412179dd8a7e155b64bf Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Tue, 16 Jan 2024 16:17:00 +0100 Subject: [PATCH 007/313] Remove irrelevant tests. Now parse method is a static method of the class, which means that it cannot be accessed from an object instance of that class. --- .../spec/languageProcessing/values/ClauseSpec.js | 6 ------ .../spec/languageProcessing/values/SentenceSpec.js | 10 ---------- 2 files changed, 16 deletions(-) diff --git a/packages/yoastseo/spec/languageProcessing/values/ClauseSpec.js b/packages/yoastseo/spec/languageProcessing/values/ClauseSpec.js index 96487b4dc36..436e286a7f5 100644 --- a/packages/yoastseo/spec/languageProcessing/values/ClauseSpec.js +++ b/packages/yoastseo/spec/languageProcessing/values/ClauseSpec.js @@ -40,11 +40,5 @@ describe( "a test for serializing and parsing a Clause class instance", function isPassive: false, participles: [], } ); - expect( mockClause.parse( mockClause.serialize() ) ).toEqual( { - _clauseText: "The cat is loved.", - _auxiliaries: [ "is" ], - _isPassive: false, - _participles: [], - } ); } ); } ); diff --git a/packages/yoastseo/spec/languageProcessing/values/SentenceSpec.js b/packages/yoastseo/spec/languageProcessing/values/SentenceSpec.js index 80bf8ad6d62..20689cd8bf4 100644 --- a/packages/yoastseo/spec/languageProcessing/values/SentenceSpec.js +++ b/packages/yoastseo/spec/languageProcessing/values/SentenceSpec.js @@ -64,15 +64,5 @@ describe( "Creates a sentence object", function() { isPassive: true, sentenceText: "Cats are adored.", } ); - expect( sentence.parse( sentence.serialize() ) ).toEqual( { - _clauses: [ - { _auxiliaries: [ "are" ], - _clauseText: "Cats are adored", - _isPassive: true, - _participles: [], - } ], - _isPassive: true, - _sentenceText: "Cats are adored.", - } ); } ); } ); From 9d83ec165a3f397fa27970491a5f19a439795916 Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Tue, 16 Jan 2024 16:24:37 +0100 Subject: [PATCH 008/313] refactor AssessorPresenter to use class --- packages/yoastseo/src/scoring/renderers/AssessorPresenter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js b/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js index c79652732d4..9089ebda211 100644 --- a/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js +++ b/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js @@ -5,7 +5,6 @@ import { assessmentPresenterResult as template } from "../../snippetPreview/temp import scoreToRating from "../interpreters/scoreToRating.js"; import createConfig from "../../config/presenter.js"; - /** * Represents the AssessorPresenter. */ @@ -22,7 +21,7 @@ class AssessorPresenter { * * @constructor */ - construct( args ) { + constructor( args ) { this.keyword = args.keyword; this.assessor = args.assessor; this.output = args.targets.output; From 0bc4453899cb3053a37e55f1735075236d9b2e8f Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Tue, 16 Jan 2024 16:43:13 +0100 Subject: [PATCH 009/313] Fix the way we access the `parse` method --- packages/js/src/insights/initializer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/src/insights/initializer.js b/packages/js/src/insights/initializer.js index d07463b6e7d..9c8b21169aa 100644 --- a/packages/js/src/insights/initializer.js +++ b/packages/js/src/insights/initializer.js @@ -23,7 +23,7 @@ const createUpdater = () => { * @returns {void} */ return () => { - const paper = Paper.prototype.parse( collectData() ); + const paper = Paper.parse( collectData() ); runResearch( "readingTime", paper ).then( response => setEstimatedReadingTime( response.result ) ); runResearch( "getFleschReadingScore", paper ).then( response => { From 65616ad81e7572c989832b95699cd70a772b874f Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Wed, 17 Apr 2024 13:45:20 +0200 Subject: [PATCH 010/313] Code scouting fixes --- .../src/scoring/renderers/AssessorPresenter.js | 15 +++++++-------- packages/yoastseo/src/values/AssessmentResult.js | 10 ++++------ packages/yoastseo/src/values/Mark.js | 2 +- packages/yoastseo/src/values/Paper.js | 4 ++-- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js b/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js index 9089ebda211..e0c48d43297 100644 --- a/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js +++ b/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js @@ -1,4 +1,3 @@ -/* eslint-disable tree-shaking/no-side-effects-in-initialization */ import { __ } from "@wordpress/i18n"; import { difference, forEach, isNumber, isObject, isUndefined } from "lodash-es"; import { assessmentPresenterResult as template } from "../../snippetPreview/templates.js"; @@ -158,7 +157,7 @@ class AssessorPresenter { } /** - * Excludes items from the results that are present in the exclude array. + * Excludes items from the results that are present in the `exclude` array. * * @param {Array} results Array containing the items to filter through. * @param {Array} exclude Array of results to exclude. @@ -169,20 +168,20 @@ class AssessorPresenter { } /** - * Sorts results based on their score property and always places items considered to be unsortable, at the top. + * Sorts results based on their score property and always places items considered to be non-sortable, at the top. * * @param {Array} results Array containing the results that need to be sorted. * @returns {Array} Array containing the sorted results. */ sort( results ) { - const unsortables = this.getUndefinedScores( results ); - const sortables = this.excludeFromResults( results, unsortables ); + const nonSortables = this.getUndefinedScores( results ); + const sortables = this.excludeFromResults( results, nonSortables ); sortables.sort( function( a, b ) { return a.score - b.score; } ); - return unsortables.concat( sortables ); + return nonSortables.concat( sortables ); } /** @@ -200,7 +199,7 @@ class AssessorPresenter { /** * Creates a rating object based on the item that is being passed. * - * @param {AssessmentResult} item The item to check and create a rating object from. + * @param {Object} item The item to check and create a rating object from. * @returns {Object} Object containing a parsed item, including a colored indicator. */ addRating( item ) { @@ -311,7 +310,7 @@ class AssessorPresenter { /** * Adds event handlers to the mark buttons * - * @param {Array} scores The list of rendered scores. + * @param {Object} scores The list of rendered scores. * * @returns {void} */ diff --git a/packages/yoastseo/src/values/AssessmentResult.js b/packages/yoastseo/src/values/AssessmentResult.js index a8ab557ad31..10ae3f01cfd 100644 --- a/packages/yoastseo/src/values/AssessmentResult.js +++ b/packages/yoastseo/src/values/AssessmentResult.js @@ -7,9 +7,7 @@ import Mark from "./Mark"; * * @returns {Array} A list of empty marks. */ -const emptyMarker = function() { - return []; -}; +const emptyMarker = () => []; /** * Represents the assessment result. @@ -172,12 +170,12 @@ class AssessmentResult { } /** - * Returns whether or not this result has a marker that can be used to mark for a given Paper + * Returns whether this result has a marker that can be used to mark for a given Paper. * - * @returns {boolean} Whether or this result has a marker. + * @returns {boolean} Whether this result has a marker. */ hasMarker() { - return this._hasMarks && this._marker !== this.emptyMarker; + return this._hasMarks && this._marker !== emptyMarker; } /** diff --git a/packages/yoastseo/src/values/Mark.js b/packages/yoastseo/src/values/Mark.js index 1f5383f52f9..629f5e0dad0 100644 --- a/packages/yoastseo/src/values/Mark.js +++ b/packages/yoastseo/src/values/Mark.js @@ -56,7 +56,7 @@ class Mark { /** * Returns the position information. * - * @returns {number} The position information. + * @returns {Object} The position information. */ getPosition() { return this._properties.position; diff --git a/packages/yoastseo/src/values/Paper.js b/packages/yoastseo/src/values/Paper.js index e9e385f8800..0f5fac7246f 100644 --- a/packages/yoastseo/src/values/Paper.js +++ b/packages/yoastseo/src/values/Paper.js @@ -61,7 +61,7 @@ class Paper { attributes.slug = attributes.url || attributes.slug; } - const onlyLetters = attributes.keyword.replace( /[‘’“”"'.?!:;,¿¡«»&*@#±^%|~`[\](){}⟨⟩<>/\\–\-\u2014\u00d7\u002b\u0026\s]/g, "" ); + const onlyLetters = attributes.keyword.replace( /[‘’“”"'.?!:;,¿¡«»&*@#±^%|~`[\](){}⟨⟩<>/\\–\-\u2014\u00d7\u002b\s]/g, "" ); if ( isEmpty( onlyLetters ) ) { attributes.keyword = defaultAttributes.keyword; @@ -181,7 +181,7 @@ class Paper { /** * Returns the title width in pixels, or an empty string of no title width in pixels is available. - * @returns {string} Returns the title + * @returns {number} Returns the title */ getTitleWidth() { return this._attributes.titleWidth; From bf0e33de98694e66765582ea64ec075cf613c380 Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Thu, 18 Apr 2024 09:47:53 +0200 Subject: [PATCH 011/313] Uses class syntax for App --- packages/yoastseo/src/app.js | 1235 +++++++++++++++++----------------- 1 file changed, 619 insertions(+), 616 deletions(-) diff --git a/packages/yoastseo/src/app.js b/packages/yoastseo/src/app.js index fece3aa9037..4460b24bfbd 100644 --- a/packages/yoastseo/src/app.js +++ b/packages/yoastseo/src/app.js @@ -1,7 +1,7 @@ import { setLocaleData } from "@wordpress/i18n"; import { debounce, defaultsDeep, forEach, isArray, isEmpty, isFunction, isObject, isString, isUndefined, merge, noop, throttle } from "lodash-es"; import MissingArgument from "./errors/missingArgument"; -import { measureTextWidth } from "./helpers/createMeasurementElement.js"; +import { measureTextWidth } from "./helpers"; import removeHtmlBlocks from "./languageProcessing/helpers/html/htmlParser.js"; import Pluggable from "./pluggable.js"; @@ -234,726 +234,729 @@ function verifyArguments( args ) { * * @constructor */ -var App = function( args ) { - if ( ! isObject( args ) ) { - args = {}; - } - - defaultsDeep( args, defaults ); - - verifyArguments( args ); +class App { + /** + * Creates a new App instance. + * @param {Object} args The arguments. + */ + constructor( args ) { + if ( ! isObject( args ) ) { + args = {}; + } - this.config = args; + defaultsDeep( args, defaults ); - if ( args.debouncedRefresh === true ) { - this.refresh = debounce( this.refresh.bind( this ), inputDebounceDelay ); - } - this._pureRefresh = throttle( this._pureRefresh.bind( this ), this.config.typeDelay ); + verifyArguments( args ); - this.callbacks = this.config.callbacks; + this.config = args; - setLocaleData( this.config.translations.locale_data[ "wordpress-seo" ], "wordpress-seo" ); + if ( args.debouncedRefresh === true ) { + this.refresh = debounce( this.refresh.bind( this ), inputDebounceDelay ); + } + this._pureRefresh = throttle( this._pureRefresh.bind( this ), this.config.typeDelay ); - this.initializeAssessors( args ); + this.callbacks = this.config.callbacks; - this.pluggable = new Pluggable( this ); + setLocaleData( this.config.translations.locale_data[ "wordpress-seo" ], "wordpress-seo" ); - this.getData(); + this.initializeAssessors( args ); - this.defaultOutputElement = this.getDefaultOutputElement( args ); + this.pluggable = new Pluggable( this ); - if ( this.defaultOutputElement !== "" ) { - this.showLoadingDialog(); - } + this.getData(); - if ( isValidSnippetPreview( args.snippetPreview ) ) { - this.snippetPreview = args.snippetPreview; + this.defaultOutputElement = this.getDefaultOutputElement( args ); - /* Hack to make sure the snippet preview always has a reference to this App. This way we solve the circular - dependency issue. In the future this should be solved by the snippet preview not having a reference to the - app.*/ - if ( this.snippetPreview.refObj !== this ) { - this.snippetPreview.refObj = this; + if ( this.defaultOutputElement !== "" ) { + this.showLoadingDialog(); } - } else if ( args.hasSnippetPreview ) { - this.snippetPreview = createDefaultSnippetPreview.call( this ); - } - - this._assessorOptions = { - useCornerStone: false, - }; - this.initSnippetPreview(); - this.initAssessorPresenters(); -}; + if ( isValidSnippetPreview( args.snippetPreview ) ) { + this.snippetPreview = args.snippetPreview; + + /* Hack to make sure the snippet preview always has a reference to this App. This way we solve the circular + dependency issue. In the future this should be solved by the snippet preview not having a reference to the + app.*/ + if ( this.snippetPreview.refObj !== this ) { + this.snippetPreview.refObj = this; + } + } else if ( args.hasSnippetPreview ) { + this.snippetPreview = createDefaultSnippetPreview.call( this ); + } -/** - * Returns the default output element based on which analyses are active. - * - * @param {Object} args The arguments passed to the App. - * @returns {string} The ID of the target that is active. - */ -App.prototype.getDefaultOutputElement = function( args ) { - if ( args.keywordAnalysisActive ) { - return args.targets.output; - } + this._assessorOptions = { + useCornerStone: false, + }; - if ( args.contentAnalysisActive ) { - return args.targets.contentOutput; + this.initSnippetPreview(); + this.initAssessorPresenters(); } - return ""; -}; - -/** - * Sets the assessors based on the assessor options and refreshes them. - * - * @param {Object} assessorOptions The specific options. - * @returns {void} - */ -App.prototype.changeAssessorOptions = function( assessorOptions ) { - this._assessorOptions = merge( this._assessorOptions, assessorOptions ); - - // Set the assessors based on the new assessor options. - this.seoAssessor = this.getSeoAssessor(); - this.contentAssessor = this.getContentAssessor(); - - // Refresh everything so the user sees the changes. - this.initAssessorPresenters(); - this.refresh(); -}; - -/** - * Returns an instance of the seo assessor to use. - * - * @returns {Assessor} The assessor instance. - */ -App.prototype.getSeoAssessor = function() { - const { useCornerStone } = this._assessorOptions; - - const assessor = useCornerStone ? this.cornerStoneSeoAssessor : this.defaultSeoAssessor; + /** + * Returns the default output element based on which analyses are active. + * + * @param {Object} args The arguments passed to the App. + * @returns {string} The ID of the target that is active. + */ + getDefaultOutputElement( args ) { + if ( args.keywordAnalysisActive ) { + return args.targets.output; + } - return assessor; -}; + if ( args.contentAnalysisActive ) { + return args.targets.contentOutput; + } -/** - * Returns an instance of the content assessor to use. - * - * @returns {Assessor} The assessor instance. - */ -App.prototype.getContentAssessor = function() { - const { useCornerStone } = this._assessorOptions; - return useCornerStone ? this.cornerStoneContentAssessor : this.defaultContentAssessor; -}; + return ""; + } + + /** + * Sets the assessors based on the assessor options and refreshes them. + * + * @param {Object} assessorOptions The specific options. + * @returns {void} + */ + changeAssessorOptions( assessorOptions ) { + this._assessorOptions = merge( this._assessorOptions, assessorOptions ); + + // Set the assessors based on the new assessor options. + this.seoAssessor = this.getSeoAssessor(); + this.contentAssessor = this.getContentAssessor(); + + // Refresh everything so the user sees the changes. + this.initAssessorPresenters(); + this.refresh(); + } + + /** + * Returns an instance of the seo assessor to use. + * + * @returns {Assessor} The assessor instance. + */ + getSeoAssessor() { + const { useCornerStone } = this._assessorOptions; + return useCornerStone ? this.cornerStoneSeoAssessor : this.defaultSeoAssessor; + } + + /** + * Returns an instance of the content assessor to use. + * + * @returns {Assessor} The assessor instance. + */ + getContentAssessor() { + const { useCornerStone } = this._assessorOptions; + return useCornerStone ? this.cornerStoneContentAssessor : this.defaultContentAssessor; + } + + /** + * Initializes assessors based on if the respective analysis is active. + * + * @param {Object} args The arguments passed to the App. + * @returns {void} + */ + initializeAssessors( args ) { + this.initializeSEOAssessor( args ); + this.initializeContentAssessor( args ); + } + + /** + * Initializes the SEO assessor. + * + * @param {Object} args The arguments passed to the App. + * @returns {void} + */ + initializeSEOAssessor( args ) { + if ( ! args.keywordAnalysisActive ) { + return; + } -/** - * Initializes assessors based on if the respective analysis is active. - * - * @param {Object} args The arguments passed to the App. - * @returns {void} - */ -App.prototype.initializeAssessors = function( args ) { - this.initializeSEOAssessor( args ); - this.initializeContentAssessor( args ); -}; + this.defaultSeoAssessor = new SEOAssessor( { marker: this.config.marker } ); + this.cornerStoneSeoAssessor = new CornerstoneSEOAssessor( { marker: this.config.marker } ); -/** - * Initializes the SEO assessor. - * - * @param {Object} args The arguments passed to the App. - * @returns {void} - */ -App.prototype.initializeSEOAssessor = function( args ) { - if ( ! args.keywordAnalysisActive ) { - return; + // Set the assessor + if ( isUndefined( args.seoAssessor ) ) { + this.seoAssessor = this.defaultSeoAssessor; + } else { + this.seoAssessor = args.seoAssessor; + } } - this.defaultSeoAssessor = new SEOAssessor( { marker: this.config.marker } ); - this.cornerStoneSeoAssessor = new CornerstoneSEOAssessor( { marker: this.config.marker } ); + /** + * Initializes the content assessor. + * + * @param {Object} args The arguments passed to the App. + * @returns {void} + */ + initializeContentAssessor( args ) { + if ( ! args.contentAnalysisActive ) { + return; + } - // Set the assessor - if ( isUndefined( args.seoAssessor ) ) { - this.seoAssessor = this.defaultSeoAssessor; - } else { - this.seoAssessor = args.seoAssessor; - } -}; + this.defaultContentAssessor = new ContentAssessor( { marker: this.config.marker, locale: this.config.locale } ); + this.cornerStoneContentAssessor = new CornerstoneContentAssessor( { marker: this.config.marker, locale: this.config.locale } ); -/** - * Initializes the content assessor. - * - * @param {Object} args The arguments passed to the App. - * @returns {void} - */ -App.prototype.initializeContentAssessor = function( args ) { - if ( ! args.contentAnalysisActive ) { - return; + // Set the content assessor + if ( isUndefined( args._contentAssessor ) ) { + this.contentAssessor = this.defaultContentAssessor; + } else { + this.contentAssessor = args._contentAssessor; + } } - this.defaultContentAssessor = new ContentAssessor( { marker: this.config.marker, locale: this.config.locale } ); - this.cornerStoneContentAssessor = new CornerstoneContentAssessor( { marker: this.config.marker, locale: this.config.locale } ); + /** + * Extend the config with defaults. + * + * @param {Object} args The arguments to be extended. + * @returns {Object} args The extended arguments. + */ + extendConfig( args ) { + args.sampleText = this.extendSampleText( args.sampleText ); + args.locale = args.locale || "en_US"; - // Set the content assessor - if ( isUndefined( args._contentAssessor ) ) { - this.contentAssessor = this.defaultContentAssessor; - } else { - this.contentAssessor = args._contentAssessor; + return args; } -}; -/** - * Extend the config with defaults. - * - * @param {Object} args The arguments to be extended. - * @returns {Object} args The extended arguments. - */ -App.prototype.extendConfig = function( args ) { - args.sampleText = this.extendSampleText( args.sampleText ); - args.locale = args.locale || "en_US"; + /** + * Extend sample text config with defaults. + * + * @param {Object} sampleText The sample text to be extended. + * @returns {Object} sampleText The extended sample text. + */ + extendSampleText( sampleText ) { + var defaultSampleText = defaults.sampleText; - return args; -}; + if ( isUndefined( sampleText ) ) { + return defaultSampleText; + } -/** - * Extend sample text config with defaults. - * - * @param {Object} sampleText The sample text to be extended. - * @returns {Object} sampleText The extended sample text. - */ -App.prototype.extendSampleText = function( sampleText ) { - var defaultSampleText = defaults.sampleText; + for ( var key in sampleText ) { + if ( isUndefined( sampleText[ key ] ) ) { + sampleText[ key ] = defaultSampleText[ key ]; + } + } - if ( isUndefined( sampleText ) ) { - return defaultSampleText; + return sampleText; } - for ( var key in sampleText ) { - if ( isUndefined( sampleText[ key ] ) ) { - sampleText[ key ] = defaultSampleText[ key ]; + /** + * Registers a custom data callback. + * + * @param {Function} callback The callback to register. + * + * @returns {void} + */ + registerCustomDataCallback( callback ) { + if ( ! this.callbacks.custom ) { + this.callbacks.custom = []; } - } - return sampleText; -}; - -/** - * Registers a custom data callback. - * - * @param {Function} callback The callback to register. - * - * @returns {void} - */ -App.prototype.registerCustomDataCallback = function( callback ) { - if ( ! this.callbacks.custom ) { - this.callbacks.custom = []; + if ( isFunction( callback ) ) { + this.callbacks.custom.push( callback ); + } } - if ( isFunction( callback ) ) { - this.callbacks.custom.push( callback ); - } -}; - -/** - * Retrieves data from the callbacks.getData and applies modification to store these in this.rawData. - * - * @returns {void} - */ -App.prototype.getData = function() { - this.rawData = this.callbacks.getData(); + /** + * Retrieves data from the callbacks.getData and applies modification to store these in this.rawData. + * + * @returns {void} + */ + getData() { + this.rawData = this.callbacks.getData(); - // Add the custom data to the raw data. - if ( isArray( this.callbacks.custom ) ) { - this.callbacks.custom.forEach( ( customCallback ) => { - const customData = customCallback(); + // Add the custom data to the raw data. + if ( isArray( this.callbacks.custom ) ) { + this.callbacks.custom.forEach( ( customCallback ) => { + const customData = customCallback(); - this.rawData = merge( this.rawData, customData ); - } ); - } - - if ( this.hasSnippetPreview() ) { - // Gets the data FOR the analyzer - var data = this.snippetPreview.getAnalyzerData(); + this.rawData = merge( this.rawData, customData ); + } ); + } - this.rawData.metaTitle = data.title; - this.rawData.url = data.url; - this.rawData.meta = data.metaDesc; - } + if ( this.hasSnippetPreview() ) { + // Gets the data FOR the analyzer + var data = this.snippetPreview.getAnalyzerData(); - if ( this.pluggable.loaded ) { - this.rawData.metaTitle = this.pluggable._applyModifications( "data_page_title", this.rawData.metaTitle ); - this.rawData.meta = this.pluggable._applyModifications( "data_meta_desc", this.rawData.meta ); - } + this.rawData.metaTitle = data.title; + this.rawData.url = data.url; + this.rawData.meta = data.metaDesc; + } - this.rawData.titleWidth = measureTextWidth( this.rawData.metaTitle ); + if ( this.pluggable.loaded ) { + this.rawData.metaTitle = this.pluggable._applyModifications( "data_page_title", this.rawData.metaTitle ); + this.rawData.meta = this.pluggable._applyModifications( "data_meta_desc", this.rawData.meta ); + } - this.rawData.locale = this.config.locale; -}; + this.rawData.titleWidth = measureTextWidth( this.rawData.metaTitle ); -/** - * Refreshes the analyzer and output of the analyzer, is debounced for a better experience. - * - * @returns {void} - */ -App.prototype.refresh = function() { - // Until all plugins are loaded, do not trigger a refresh. - if ( ! this.pluggable.loaded ) { - return; + this.rawData.locale = this.config.locale; } - this._pureRefresh(); -}; - -/** - * Refreshes the analyzer and output of the analyzer, is throttled to prevent performance issues. - * - * @returns {void} - * - * @private - */ -App.prototype._pureRefresh = function() { - this.getData(); - this.runAnalyzer(); -}; - -/** - * Determines whether or not this app has a snippet preview. - * - * @returns {boolean} Whether or not this app has a snippet preview. - */ -App.prototype.hasSnippetPreview = function() { - return this.snippetPreview !== null && ! isUndefined( this.snippetPreview ); -}; - -/** - * Initializes the snippet preview for this App. - * - * @returns {void} - */ -App.prototype.initSnippetPreview = function() { - if ( this.hasSnippetPreview() ) { - this.snippetPreview.renderTemplate(); - this.snippetPreview.callRegisteredEventBinder(); - this.snippetPreview.bindEvents(); - this.snippetPreview.init(); - } -}; + /** + * Refreshes the analyzer and output of the analyzer, is debounced for a better experience. + * + * @returns {void} + */ + refresh() { + // Until all plugins are loaded, do not trigger a refresh. + if ( ! this.pluggable.loaded ) { + return; + } -/** - * Initializes the assessor presenters for content and SEO. - * - * @returns {void} - */ -App.prototype.initAssessorPresenters = function() { - // Pass the assessor result through to the formatter - if ( ! isUndefined( this.config.targets.output ) ) { - this.seoAssessorPresenter = new AssessorPresenter( { - targets: { - output: this.config.targets.output, - }, - assessor: this.seoAssessor, - } ); + this._pureRefresh(); + } + + /** + * Refreshes the analyzer and output of the analyzer, is throttled to prevent performance issues. + * + * @returns {void} + * + * @private + */ + _pureRefresh() { + this.getData(); + this.runAnalyzer(); + } + + /** + * Determines whether or not this app has a snippet preview. + * + * @returns {boolean} Whether or not this app has a snippet preview. + */ + hasSnippetPreview() { + return this.snippetPreview !== null && ! isUndefined( this.snippetPreview ); + } + + /** + * Initializes the snippet preview for this App. + * + * @returns {void} + */ + initSnippetPreview() { + if ( this.hasSnippetPreview() ) { + this.snippetPreview.renderTemplate(); + this.snippetPreview.callRegisteredEventBinder(); + this.snippetPreview.bindEvents(); + this.snippetPreview.init(); + } } - if ( ! isUndefined( this.config.targets.contentOutput ) ) { + /** + * Initializes the assessor presenters for content and SEO. + * + * @returns {void} + */ + initAssessorPresenters() { // Pass the assessor result through to the formatter - this.contentAssessorPresenter = new AssessorPresenter( { - targets: { - output: this.config.targets.contentOutput, - }, - assessor: this.contentAssessor, - } ); - } -}; - -/** - * Binds the refresh function to the input of the targetElement on the page. - * - * @returns {void} - */ -App.prototype.bindInputEvent = function() { - for ( var i = 0; i < this.config.elementTarget.length; i++ ) { - var elem = document.getElementById( this.config.elementTarget[ i ] ); - elem.addEventListener( "input", this.refresh.bind( this ) ); - } -}; + if ( ! isUndefined( this.config.targets.output ) ) { + this.seoAssessorPresenter = new AssessorPresenter( { + targets: { + output: this.config.targets.output, + }, + assessor: this.seoAssessor, + } ); + } -/** - * Runs the rerender function of the snippetPreview if that object is defined. - * - * @returns {void} - */ -App.prototype.reloadSnippetText = function() { - if ( this.hasSnippetPreview() ) { - this.snippetPreview.reRender(); + if ( ! isUndefined( this.config.targets.contentOutput ) ) { + // Pass the assessor result through to the formatter + this.contentAssessorPresenter = new AssessorPresenter( { + targets: { + output: this.config.targets.contentOutput, + }, + assessor: this.contentAssessor, + } ); + } } -}; - -/** - * Sets the startTime timestamp. - * - * @returns {void} - */ -App.prototype.startTime = function() { - this.startTimestamp = new Date().getTime(); -}; -/** - * Sets the endTime timestamp and compares with startTime to determine typeDelayincrease. - * - * @returns {void} - */ -App.prototype.endTime = function() { - this.endTimestamp = new Date().getTime(); - if ( this.endTimestamp - this.startTimestamp > this.config.typeDelay ) { - if ( this.config.typeDelay < ( this.config.maxTypeDelay - this.config.typeDelayStep ) ) { - this.config.typeDelay += this.config.typeDelayStep; + /** + * Binds the refresh function to the input of the targetElement on the page. + * + * @returns {void} + */ + bindInputEvent() { + for ( var i = 0; i < this.config.elementTarget.length; i++ ) { + var elem = document.getElementById( this.config.elementTarget[ i ] ); + elem.addEventListener( "input", this.refresh.bind( this ) ); } } -}; -/** - * Inits a new pageAnalyzer with the inputs from the getInput function and calls the scoreFormatter - * to format outputs. - * - * @returns {void} - */ -App.prototype.runAnalyzer = function() { - if ( this.pluggable.loaded === false ) { - return; + /** + * Runs the rerender function of the snippetPreview if that object is defined. + * + * @returns {void} + */ + reloadSnippetText() { + if ( this.hasSnippetPreview() ) { + this.snippetPreview.reRender(); + } } - if ( this.config.dynamicDelay ) { - this.startTime(); + /** + * Sets the startTime timestamp. + * + * @returns {void} + */ + startTime() { + this.startTimestamp = new Date().getTime(); + } + + /** + * Sets the endTime timestamp and compares with startTime to determine typeDelayincrease. + * + * @returns {void} + */ + endTime() { + this.endTimestamp = new Date().getTime(); + if ( this.endTimestamp - this.startTimestamp > this.config.typeDelay ) { + if ( this.config.typeDelay < ( this.config.maxTypeDelay - this.config.typeDelayStep ) ) { + this.config.typeDelay += this.config.typeDelayStep; + } + } } - this.analyzerData = this.modifyData( this.rawData ); - - if ( this.hasSnippetPreview() ) { - this.snippetPreview.refresh(); - } + /** + * Inits a new pageAnalyzer with the inputs from the getInput function and calls the scoreFormatter + * to format outputs. + * + * @returns {void} + */ + runAnalyzer() { + if ( this.pluggable.loaded === false ) { + return; + } - let text = this.analyzerData.text; + if ( this.config.dynamicDelay ) { + this.startTime(); + } - // Insert HTML stripping code - text = removeHtmlBlocks( text ); + this.analyzerData = this.modifyData( this.rawData ); - let titleWidth = this.analyzerData.titleWidth; - if ( this.hasSnippetPreview() ) { - titleWidth = this.snippetPreview.getTitleWidth(); - } + if ( this.hasSnippetPreview() ) { + this.snippetPreview.refresh(); + } - // Create a paper object for the Researcher - this.paper = new Paper( text, { - keyword: this.analyzerData.keyword, - synonyms: this.analyzerData.synonyms, - description: this.analyzerData.meta, - slug: this.analyzerData.slug, - title: this.analyzerData.metaTitle, - titleWidth: titleWidth, - locale: this.config.locale, - permalink: this.analyzerData.permalink, - } ); + let text = this.analyzerData.text; - this.config.researcher.setPaper( this.paper ); + // Insert HTML stripping code + text = removeHtmlBlocks( text ); - this.runKeywordAnalysis(); + let titleWidth = this.analyzerData.titleWidth; + if ( this.hasSnippetPreview() ) { + titleWidth = this.snippetPreview.getTitleWidth(); + } - this.runContentAnalysis(); + // Create a paper object for the Researcher + this.paper = new Paper( text, { + keyword: this.analyzerData.keyword, + synonyms: this.analyzerData.synonyms, + description: this.analyzerData.meta, + slug: this.analyzerData.slug, + title: this.analyzerData.metaTitle, + titleWidth: titleWidth, + locale: this.config.locale, + permalink: this.analyzerData.permalink, + } ); - this._renderAnalysisResults(); + this.config.researcher.setPaper( this.paper ); - if ( this.config.dynamicDelay ) { - this.endTime(); - } + this.runKeywordAnalysis(); - if ( this.hasSnippetPreview() ) { - this.snippetPreview.reRender(); - } -}; + this.runContentAnalysis(); -/** - * Runs the keyword analysis and calls the appropriate callbacks. - * - * @returns {void} - */ -App.prototype.runKeywordAnalysis = function() { - if ( this.config.keywordAnalysisActive ) { - this.seoAssessor.assess( this.paper ); - const overallSeoScore = this.seoAssessor.calculateOverallScore(); + this._renderAnalysisResults(); - if ( ! isUndefined( this.callbacks.updatedKeywordsResults ) ) { - this.callbacks.updatedKeywordsResults( this.seoAssessor.results, overallSeoScore ); + if ( this.config.dynamicDelay ) { + this.endTime(); } - if ( ! isUndefined( this.callbacks.saveScores ) ) { - this.callbacks.saveScores( overallSeoScore, this.seoAssessorPresenter ); + if ( this.hasSnippetPreview() ) { + this.snippetPreview.reRender(); } } -}; -/** - * Runs the content analysis and calls the appropriate callbacks. - * - * @returns {void} - */ -App.prototype.runContentAnalysis = function() { - if ( this.config.contentAnalysisActive ) { - this.contentAssessor.assess( this.paper ); - const overallContentScore = this.contentAssessor.calculateOverallScore(); + /** + * Runs the keyword analysis and calls the appropriate callbacks. + * + * @returns {void} + */ + runKeywordAnalysis() { + if ( this.config.keywordAnalysisActive ) { + this.seoAssessor.assess( this.paper ); + const overallSeoScore = this.seoAssessor.calculateOverallScore(); - if ( ! isUndefined( this.callbacks.updatedContentResults ) ) { - this.callbacks.updatedContentResults( this.contentAssessor.results, overallContentScore ); - } + if ( ! isUndefined( this.callbacks.updatedKeywordsResults ) ) { + this.callbacks.updatedKeywordsResults( this.seoAssessor.results, overallSeoScore ); + } - if ( ! isUndefined( this.callbacks.saveContentScore ) ) { - this.callbacks.saveContentScore( overallContentScore, this.contentAssessorPresenter ); + if ( ! isUndefined( this.callbacks.saveScores ) ) { + this.callbacks.saveScores( overallSeoScore, this.seoAssessorPresenter ); + } } } -}; - -/** - * Modifies the data with plugins before it is sent to the analyzer. - * - * @param {Object} data The data to be modified. - * @returns {Object} The data with the applied modifications. - */ -App.prototype.modifyData = function( data ) { - // Copy rawdata to lose object reference. - data = JSON.parse( JSON.stringify( data ) ); - data.text = this.pluggable._applyModifications( "content", data.text ); - data.metaTitle = this.pluggable._applyModifications( "title", data.metaTitle ); + /** + * Runs the content analysis and calls the appropriate callbacks. + * + * @returns {void} + */ + runContentAnalysis() { + if ( this.config.contentAnalysisActive ) { + this.contentAssessor.assess( this.paper ); + const overallContentScore = this.contentAssessor.calculateOverallScore(); - return data; -}; - -/** - * Function to fire the analyzer when all plugins are loaded, removes the loading dialog. - * - * @returns {void} - */ -App.prototype.pluginsLoaded = function() { - this.removeLoadingDialog(); - this.refresh(); -}; + if ( ! isUndefined( this.callbacks.updatedContentResults ) ) { + this.callbacks.updatedContentResults( this.contentAssessor.results, overallContentScore ); + } -/** - * Shows the loading dialog which shows the loading of the plugins. - * - * @returns {void} - */ -App.prototype.showLoadingDialog = function() { - var outputElement = document.getElementById( this.defaultOutputElement ); - - if ( this.defaultOutputElement !== "" && ! isEmpty( outputElement ) ) { - var dialogDiv = document.createElement( "div" ); - dialogDiv.className = "YoastSEO_msg"; - dialogDiv.id = "YoastSEO-plugin-loading"; - document.getElementById( this.defaultOutputElement ).appendChild( dialogDiv ); + if ( ! isUndefined( this.callbacks.saveContentScore ) ) { + this.callbacks.saveContentScore( overallContentScore, this.contentAssessorPresenter ); + } + } } -}; -/** - * Updates the loading plugins. Uses the plugins as arguments to show which plugins are loading. - * - * @param {Object} plugins The plugins to be parsed into the dialog. - * @returns {void} - */ -App.prototype.updateLoadingDialog = function( plugins ) { - var outputElement = document.getElementById( this.defaultOutputElement ); - - if ( this.defaultOutputElement === "" || isEmpty( outputElement ) ) { - return; + /** + * Modifies the data with plugins before it is sent to the analyzer. + * + * @param {Object} data The data to be modified. + * @returns {Object} The data with the applied modifications. + */ + modifyData( data ) { + // Copy rawdata to lose object reference. + data = JSON.parse( JSON.stringify( data ) ); + + data.text = this.pluggable._applyModifications( "content", data.text ); + data.metaTitle = this.pluggable._applyModifications( "title", data.metaTitle ); + + return data; + } + + /** + * Function to fire the analyzer when all plugins are loaded, removes the loading dialog. + * + * @returns {void} + */ + pluginsLoaded() { + this.removeLoadingDialog(); + this.refresh(); + } + + /** + * Shows the loading dialog which shows the loading of the plugins. + * + * @returns {void} + */ + showLoadingDialog() { + var outputElement = document.getElementById( this.defaultOutputElement ); + + if ( this.defaultOutputElement !== "" && ! isEmpty( outputElement ) ) { + var dialogDiv = document.createElement( "div" ); + dialogDiv.className = "YoastSEO_msg"; + dialogDiv.id = "YoastSEO-plugin-loading"; + document.getElementById( this.defaultOutputElement ).appendChild( dialogDiv ); + } } - var dialog = document.getElementById( "YoastSEO-plugin-loading" ); - dialog.textContent = ""; + /** + * Updates the loading plugins. Uses the plugins as arguments to show which plugins are loading. + * + * @param {Object} plugins The plugins to be parsed into the dialog. + * @returns {void} + */ + updateLoadingDialog( plugins ) { + var outputElement = document.getElementById( this.defaultOutputElement ); - forEach( plugins, function( plugin, pluginName ) { - dialog.innerHTML += "" + pluginName + "" + plugin.status + "
"; - } ); + if ( this.defaultOutputElement === "" || isEmpty( outputElement ) ) { + return; + } - dialog.innerHTML += ""; -}; + var dialog = document.getElementById( "YoastSEO-plugin-loading" ); + dialog.textContent = ""; -/** - * Removes the pluging load dialog. - * - * @returns {void} - */ -App.prototype.removeLoadingDialog = function() { - var outputElement = document.getElementById( this.defaultOutputElement ); - var loadingDialog = document.getElementById( "YoastSEO-plugin-loading" ); + forEach( plugins, function( plugin, pluginName ) { + dialog.innerHTML += "" + pluginName + "" + plugin.status + "
"; + } ); - if ( ( this.defaultOutputElement !== "" && ! isEmpty( outputElement ) ) && ! isEmpty( loadingDialog ) ) { - document.getElementById( this.defaultOutputElement ).removeChild( document.getElementById( "YoastSEO-plugin-loading" ) ); + dialog.innerHTML += ""; } -}; -// ***** PLUGGABLE PUBLIC DSL ***** // + /** + * Removes the pluging load dialog. + * + * @returns {void} + */ + removeLoadingDialog() { + var outputElement = document.getElementById( this.defaultOutputElement ); + var loadingDialog = document.getElementById( "YoastSEO-plugin-loading" ); -/** - * Delegates to `YoastSEO.app.pluggable.registerPlugin` - * - * @param {string} pluginName The name of the plugin to be registered. - * @param {object} options The options object. - * @param {string} options.status The status of the plugin being registered. Can either be "loading" or "ready". - * @returns {boolean} Whether or not it was successfully registered. - */ -App.prototype.registerPlugin = function( pluginName, options ) { - return this.pluggable._registerPlugin( pluginName, options ); -}; - -/** - * Delegates to `YoastSEO.app.pluggable.ready` - * - * @param {string} pluginName The name of the plugin to check. - * @returns {boolean} Whether or not the plugin is ready. - */ -App.prototype.pluginReady = function( pluginName ) { - return this.pluggable._ready( pluginName ); -}; - -/** - * Delegates to `YoastSEO.app.pluggable.reloaded` - * - * @param {string} pluginName The name of the plugin to reload - * @returns {boolean} Whether or not the plugin was reloaded. - */ -App.prototype.pluginReloaded = function( pluginName ) { - return this.pluggable._reloaded( pluginName ); -}; - -/** - * Delegates to `YoastSEO.app.pluggable.registerModification`. - * - * @param {string} modification The name of the filter - * @param {function} callable The callable function - * @param {string} pluginName The plugin that is registering the modification. - * @param {number} [priority] Used to specify the order in which the callables associated with a particular filter are called. - * Lower numbers correspond with earlier execution. - * - * @returns {boolean} Whether or not the modification was successfully registered. - */ -App.prototype.registerModification = function( modification, callable, pluginName, priority ) { - return this.pluggable._registerModification( modification, callable, pluginName, priority ); -}; - -/** - * Registers a custom assessment for use in the analyzer, this will result in a new line in the analyzer results. - * The function needs to use the assessmentresult to return an result based on the contents of the page/posts. - * - * Score 0 results in a grey circle if it is not explicitly set by using setscore - * Scores 0, 1, 2, 3 and 4 result in a red circle - * Scores 6 and 7 result in a yellow circle - * Scores 8, 9 and 10 result in a green circle - * - * @param {string} name Name of the test. - * @param {function} assessment The assessment to run - * @param {string} pluginName The plugin that is registering the test. - * @returns {boolean} Whether or not the test was successfully registered. - */ -App.prototype.registerAssessment = function( name, assessment, pluginName ) { - if ( ! isUndefined( this.seoAssessor ) ) { - return this.pluggable._registerAssessment( this.defaultSeoAssessor, name, assessment, pluginName ) && - this.pluggable._registerAssessment( this.cornerStoneSeoAssessor, name, assessment, pluginName ); + if ( ( this.defaultOutputElement !== "" && ! isEmpty( outputElement ) ) && ! isEmpty( loadingDialog ) ) { + document.getElementById( this.defaultOutputElement ).removeChild( document.getElementById( "YoastSEO-plugin-loading" ) ); + } } -}; -/** - * Disables markers visually in the UI. - * - * @returns {void} - */ -App.prototype.disableMarkers = function() { - if ( ! isUndefined( this.seoAssessorPresenter ) ) { - this.seoAssessorPresenter.disableMarker(); + // ***** PLUGGABLE PUBLIC DSL ***** // + /** + * Delegates to `YoastSEO.app.pluggable.registerPlugin` + * + * @param {string} pluginName The name of the plugin to be registered. + * @param {object} options The options object. + * @param {string} options.status The status of the plugin being registered. Can either be "loading" or "ready". + * @returns {boolean} Whether or not it was successfully registered. + */ + registerPlugin( pluginName, options ) { + return this.pluggable._registerPlugin( pluginName, options ); + } + + /** + * Delegates to `YoastSEO.app.pluggable.ready` + * + * @param {string} pluginName The name of the plugin to check. + * @returns {boolean} Whether or not the plugin is ready. + */ + pluginReady( pluginName ) { + return this.pluggable._ready( pluginName ); + } + + /** + * Delegates to `YoastSEO.app.pluggable.reloaded` + * + * @param {string} pluginName The name of the plugin to reload + * @returns {boolean} Whether or not the plugin was reloaded. + */ + pluginReloaded( pluginName ) { + return this.pluggable._reloaded( pluginName ); + } + + /** + * Delegates to `YoastSEO.app.pluggable.registerModification`. + * + * @param {string} modification The name of the filter + * @param {function} callable The callable function + * @param {string} pluginName The plugin that is registering the modification. + * @param {number} [priority] Used to specify the order in which the callables associated with a particular filter are called. + * Lower numbers correspond with earlier execution. + * + * @returns {boolean} Whether or not the modification was successfully registered. + */ + registerModification( modification, callable, pluginName, priority ) { + return this.pluggable._registerModification( modification, callable, pluginName, priority ); + } + + /** + * Registers a custom assessment for use in the analyzer, this will result in a new line in the analyzer results. + * The function needs to use the assessmentresult to return an result based on the contents of the page/posts. + * + * Score 0 results in a grey circle if it is not explicitly set by using setscore + * Scores 0, 1, 2, 3 and 4 result in a red circle + * Scores 6 and 7 result in a yellow circle + * Scores 8, 9 and 10 result in a green circle + * + * @param {string} name Name of the test. + * @param {function} assessment The assessment to run + * @param {string} pluginName The plugin that is registering the test. + * @returns {boolean} Whether or not the test was successfully registered. + */ + registerAssessment( name, assessment, pluginName ) { + if ( ! isUndefined( this.seoAssessor ) ) { + return this.pluggable._registerAssessment( this.defaultSeoAssessor, name, assessment, pluginName ) && + this.pluggable._registerAssessment( this.cornerStoneSeoAssessor, name, assessment, pluginName ); + } } - if ( ! isUndefined( this.contentAssessorPresenter ) ) { - this.contentAssessorPresenter.disableMarker(); - } -}; + /** + * Disables markers visually in the UI. + * + * @returns {void} + */ + disableMarkers() { + if ( ! isUndefined( this.seoAssessorPresenter ) ) { + this.seoAssessorPresenter.disableMarker(); + } -/** - * Renders the content and keyword analysis results. - * - * @returns {void} - */ -App.prototype._renderAnalysisResults = function() { - if ( this.config.contentAnalysisActive && ! isUndefined( this.contentAssessorPresenter ) ) { - this.contentAssessorPresenter.renderIndividualRatings(); - } - if ( this.config.keywordAnalysisActive && ! isUndefined( this.seoAssessorPresenter ) ) { - this.seoAssessorPresenter.setKeyword( this.paper.getKeyword() ); - this.seoAssessorPresenter.render(); + if ( ! isUndefined( this.contentAssessorPresenter ) ) { + this.contentAssessorPresenter.disableMarker(); + } } -}; -// Deprecated functions -/** - * The analyzeTimer calls the checkInputs function with a delay, so the function won't be executed - * at every keystroke checks the reference object, so this function can be called from anywhere, - * without problems with different scopes. - * - * @deprecated: 1.3 - Use this.refresh() instead. - * - * @returns {void} - */ -App.prototype.analyzeTimer = function() { - this.refresh(); -}; - -/** - * Registers a custom test for use in the analyzer, this will result in a new line in the analyzer results. The function - * has to return a result based on the contents of the page/posts. - * - * The scoring object is a special object with definitions about how to translate a result from your analysis function - * to a SEO score. - * - * Negative scores result in a red circle - * Scores 1, 2, 3, 4 and 5 result in a orange circle - * Scores 6 and 7 result in a yellow circle - * Scores 8, 9 and 10 result in a red circle - * - * @returns {void} - * - * @deprecated since version 1.2 - */ -App.prototype.registerTest = function() { - console.error( "This function is deprecated, please use registerAssessment" ); -}; - -/** - * Creates the elements for the snippetPreview - * - * @deprecated Don't create a snippet preview using this method, create it directly using the prototype and pass it as - * an argument instead. - * - * @returns {void} - */ -App.prototype.createSnippetPreview = function() { - this.snippetPreview = createDefaultSnippetPreview.call( this ); - this.initSnippetPreview(); -}; + /** + * Renders the content and keyword analysis results. + * + * @returns {void} + */ + _renderAnalysisResults() { + if ( this.config.contentAnalysisActive && ! isUndefined( this.contentAssessorPresenter ) ) { + this.contentAssessorPresenter.renderIndividualRatings(); + } + if ( this.config.keywordAnalysisActive && ! isUndefined( this.seoAssessorPresenter ) ) { + this.seoAssessorPresenter.setKeyword( this.paper.getKeyword() ); + this.seoAssessorPresenter.render(); + } + } -/** - * Switches between the cornerstone and default assessors. - * - * @deprecated 1.35.0 - Use changeAssessorOption instead. - * - * @param {boolean} useCornerStone True when cornerstone should be used. - * - * @returns {void} - */ -App.prototype.switchAssessors = function( useCornerStone ) { - // eslint-disable-next-line no-console - console.warn( "Switch assessor is deprecated since YoastSEO.js version 1.35.0" ); + // Deprecated functions + /** + * The analyzeTimer calls the checkInputs function with a delay, so the function won't be executed + * at every keystroke checks the reference object, so this function can be called from anywhere, + * without problems with different scopes. + * + * @deprecated: 1.3 - Use this.refresh() instead. + * + * @returns {void} + */ + analyzeTimer() { + this.refresh(); + } + + /** + * Registers a custom test for use in the analyzer, this will result in a new line in the analyzer results. The function + * has to return a result based on the contents of the page/posts. + * + * The scoring object is a special object with definitions about how to translate a result from your analysis function + * to a SEO score. + * + * Negative scores result in a red circle + * Scores 1, 2, 3, 4 and 5 result in a orange circle + * Scores 6 and 7 result in a yellow circle + * Scores 8, 9 and 10 result in a red circle + * + * @returns {void} + * + * @deprecated since version 1.2 + */ + registerTest() { + console.error( "This function is deprecated, please use registerAssessment" ); + } + + /** + * Creates the elements for the snippetPreview + * + * @deprecated Don't create a snippet preview using this method, create it directly using the prototype and pass it as + * an argument instead. + * + * @returns {void} + */ + createSnippetPreview() { + this.snippetPreview = createDefaultSnippetPreview.call( this ); + this.initSnippetPreview(); + } + + /** + * Switches between the cornerstone and default assessors. + * + * @deprecated 1.35.0 - Use changeAssessorOption instead. + * + * @param {boolean} useCornerStone True when cornerstone should be used. + * + * @returns {void} + */ + switchAssessors( useCornerStone ) { + // eslint-disable-next-line no-console + console.warn( "Switch assessor is deprecated since YoastSEO.js version 1.35.0" ); + + this.changeAssessorOptions( { + useCornerStone, + } ); + } +} - this.changeAssessorOptions( { - useCornerStone, - } ); -}; export default App; From 8b8e18ef248daa8924c04c701d87177f671adbb1 Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Thu, 18 Apr 2024 10:15:28 +0200 Subject: [PATCH 012/313] Uses class syntax for assessors (first pass) --- packages/yoastseo/src/scoring/assessor.js | 505 +++++++++--------- .../yoastseo/src/scoring/contentAssessor.js | 271 +++++----- .../scoring/cornerstone/contentAssessor.js | 80 ++- .../cornerstone/relatedKeywordAssessor.js | 59 +- .../src/scoring/cornerstone/seoAssessor.js | 136 +++-- .../src/scoring/inclusiveLanguageAssessor.js | 13 +- .../src/scoring/relatedKeywordAssessor.js | 49 +- .../scoring/relatedKeywordTaxonomyAssessor.js | 46 +- packages/yoastseo/src/scoring/seoAssessor.js | 77 ++- .../cornerstone/contentAssessor.js | 98 ++-- .../yoastseo/src/scoring/taxonomyAssessor.js | 16 +- 11 files changed, 648 insertions(+), 702 deletions(-) diff --git a/packages/yoastseo/src/scoring/assessor.js b/packages/yoastseo/src/scoring/assessor.js index fedab386dce..a912d0c28ac 100644 --- a/packages/yoastseo/src/scoring/assessor.js +++ b/packages/yoastseo/src/scoring/assessor.js @@ -12,292 +12,293 @@ import { build } from "../parse/build"; const ScoreRating = 9; /** - * Creates the Assessor. - * - * @param {Researcher} researcher The researcher to use in the assessor. - * @param {Object?} options The options for this assessor. - * @param {Function} options.marker The marker to pass the list of marks to. - * - * @constructor + * The Assessor is a base class for all assessors. */ -const Assessor = function( researcher, options ) { - this.type = "assessor"; - this.setResearcher( researcher ); - this._assessments = []; - - this._options = options || {}; -}; - -/** - * Checks if the researcher is defined and sets it. - * - * @param {Researcher} researcher The researcher to use in the assessor. - * - * @throws {MissingArgument} Parameter needs to be a valid researcher object. - * @returns {void} - */ -Assessor.prototype.setResearcher = function( researcher ) { - if ( isUndefined( researcher ) ) { - throw new MissingArgument( "The assessor requires a researcher." ); +class Assessor { + /** + * Creates a new Assessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + this.type = "assessor"; + this.setResearcher( researcher ); + this._assessments = []; + + this._options = options || {}; } - this._researcher = researcher; -}; -/** - * Gets all available assessments. - * @returns {object} assessment - */ -Assessor.prototype.getAvailableAssessments = function() { - return this._assessments; -}; - -/** - * Checks whether the Assessment is applicable. - * - * @param {Object} assessment The Assessment object that needs to be checked. - * @param {Paper} paper The Paper object to check against. - * @param {Researcher} [researcher] The Researcher object containing additional information. - * @returns {boolean} Whether or not the Assessment is applicable. - */ -Assessor.prototype.isApplicable = function( assessment, paper, researcher ) { - if ( assessment.hasOwnProperty( "isApplicable" ) || typeof assessment.isApplicable === "function" ) { - return assessment.isApplicable( paper, researcher ); + /** + * Checks if the researcher is defined and sets it. + * + * @param {Researcher} researcher The researcher to use in the assessor. + * + * @throws {MissingArgument} Parameter needs to be a valid researcher object. + * @returns {void} + */ + setResearcher( researcher ) { + if ( isUndefined( researcher ) ) { + throw new MissingArgument( "The assessor requires a researcher." ); + } + this._researcher = researcher; } - return true; -}; - -/** - * Determines whether an assessment has a marker. - * - * @param {Object} assessment The assessment to check for. - * @returns {boolean} Whether or not the assessment has a marker. - */ -Assessor.prototype.hasMarker = function( assessment ) { - return isFunction( this._options.marker ) && ( assessment.hasOwnProperty( "getMarks" ) || typeof assessment.getMarks === "function" ); -}; - -/** - * Returns the specific marker for this assessor. - * - * @returns {Function} The specific marker for this assessor. - */ -Assessor.prototype.getSpecificMarker = function() { - return this._options.marker; -}; + /** + * Gets all available assessments. + * @returns {object} assessment + */ + getAvailableAssessments() { + return this._assessments; + } -/** - * Returns the paper that was most recently assessed. - * - * @returns {Paper} The paper that was most recently assessed. - */ -Assessor.prototype.getPaper = function() { - return this._lastPaper; -}; + /** + * Checks whether the Assessment is applicable. + * + * @param {Object} assessment The Assessment object that needs to be checked. + * @param {Paper} paper The Paper object to check against. + * @param {Researcher} [researcher] The Researcher object containing additional information. + * @returns {boolean} Whether or not the Assessment is applicable. + */ + isApplicable( assessment, paper, researcher ) { + if ( assessment.hasOwnProperty( "isApplicable" ) || typeof assessment.isApplicable === "function" ) { + return assessment.isApplicable( paper, researcher ); + } -/** - * Returns the marker for a given assessment, composes the specific marker with the assessment getMarks function. - * - * @param {Object} assessment The assessment for which we are retrieving the composed marker. - * @param {Paper} paper The paper to retrieve the marker for. - * @param {Researcher} researcher The researcher for the paper. - * @returns {Function} A function that can mark the given paper according to the given assessment. - */ -Assessor.prototype.getMarker = function( assessment, paper, researcher ) { - const specificMarker = this._options.marker; + return true; + } - return function() { - let marks = assessment.getMarks( paper, researcher ); - marks = removeDuplicateMarks( marks ); + /** + * Determines whether an assessment has a marker. + * + * @param {Object} assessment The assessment to check for. + * @returns {boolean} Whether or not the assessment has a marker. + */ + hasMarker( assessment ) { + return isFunction( this._options.marker ) && ( assessment.hasOwnProperty( "getMarks" ) || typeof assessment.getMarks === "function" ); + } - specificMarker( paper, marks ); - }; -}; + /** + * Returns the specific marker for this assessor. + * + * @returns {Function} The specific marker for this assessor. + */ + getSpecificMarker() { + return this._options.marker; + } -/** - * Runs the researches defined in the task list or the default researches. - * - * @param {Paper} paper The paper to run assessments on. - * @returns {void} - */ -Assessor.prototype.assess = function( paper ) { - this._researcher.setPaper( paper ); + /** + * Returns the paper that was most recently assessed. + * + * @returns {Paper} The paper that was most recently assessed. + */ + getPaper() { + return this._lastPaper; + } - const languageProcessor = new LanguageProcessor( this._researcher ); - const shortcodes = paper._attributes && paper._attributes.shortcodes; - paper.setTree( build( paper, languageProcessor, shortcodes ) ); + /** + * Returns the marker for a given assessment, composes the specific marker with the assessment getMarks function. + * + * @param {Object} assessment The assessment for which we are retrieving the composed marker. + * @param {Paper} paper The paper to retrieve the marker for. + * @param {Researcher} researcher The researcher for the paper. + * @returns {Function} A function that can mark the given paper according to the given assessment. + */ + getMarker( assessment, paper, researcher ) { + const specificMarker = this._options.marker; + + return function() { + let marks = assessment.getMarks( paper, researcher ); + marks = removeDuplicateMarks( marks ); + + specificMarker( paper, marks ); + }; + } - let assessments = this.getAvailableAssessments(); - this.results = []; + /** + * Runs the researches defined in the task list or the default researches. + * + * @param {Paper} paper The paper to run assessments on. + * @returns {void} + */ + assess( paper ) { + this._researcher.setPaper( paper ); - assessments = filter( assessments, function( assessment ) { - return this.isApplicable( assessment, paper, this._researcher ); - }.bind( this ) ); + const languageProcessor = new LanguageProcessor( this._researcher ); + const shortcodes = paper._attributes && paper._attributes.shortcodes; + paper.setTree( build( paper, languageProcessor, shortcodes ) ); - this.setHasMarkers( false ); - this.results = map( assessments, this.executeAssessment.bind( this, paper, this._researcher ) ); + let assessments = this.getAvailableAssessments(); + this.results = []; - this._lastPaper = paper; -}; + assessments = filter( assessments, function( assessment ) { + return this.isApplicable( assessment, paper, this._researcher ); + }.bind( this ) ); -/** - * Sets the value of has markers with a boolean to determine if there are markers. - * - * @param {boolean} hasMarkers True when there are markers, otherwise it is false. - * @returns {void} - */ -Assessor.prototype.setHasMarkers = function( hasMarkers ) { - this._hasMarkers = hasMarkers; -}; + this.setHasMarkers( false ); + this.results = map( assessments, this.executeAssessment.bind( this, paper, this._researcher ) ); -/** - * Returns true when there are markers. - * - * @returns {boolean} Are there markers - */ -Assessor.prototype.hasMarkers = function() { - return this._hasMarkers; -}; - -/** - * Executes an assessment and returns the AssessmentResult. - * - * @param {Paper} paper The paper to pass to the assessment. - * @param {Researcher} researcher The researcher to pass to the assessment. - * @param {Object} assessment The assessment to execute. - * @returns {AssessmentResult} The result of the assessment. - */ -Assessor.prototype.executeAssessment = function( paper, researcher, assessment ) { - let result; - - try { - result = assessment.getResult( paper, researcher ); - result.setIdentifier( assessment.identifier ); + this._lastPaper = paper; + } - if ( result.hasMarks() ) { - result.marks = assessment.getMarks( paper, researcher ); - result.marks = removeDuplicateMarks( result.marks ); - } + /** + * Sets the value of has markers with a boolean to determine if there are markers. + * + * @param {boolean} hasMarkers True when there are markers, otherwise it is false. + * @returns {void} + */ + setHasMarkers( hasMarkers ) { + this._hasMarkers = hasMarkers; + } - if ( result.hasMarks() && this.hasMarker( assessment ) ) { - this.setHasMarkers( true ); + /** + * Returns true when there are markers. + * + * @returns {boolean} Are there markers + */ + hasMarkers() { + return this._hasMarkers; + } - result.setMarker( this.getMarker( assessment, paper, researcher ) ); + /** + * Executes an assessment and returns the AssessmentResult. + * + * @param {Paper} paper The paper to pass to the assessment. + * @param {Researcher} researcher The researcher to pass to the assessment. + * @param {Object} assessment The assessment to execute. + * @returns {AssessmentResult} The result of the assessment. + */ + executeAssessment( paper, researcher, assessment ) { + let result; + + try { + result = assessment.getResult( paper, researcher ); + result.setIdentifier( assessment.identifier ); + + if ( result.hasMarks() ) { + result.marks = assessment.getMarks( paper, researcher ); + result.marks = removeDuplicateMarks( result.marks ); + } + + if ( result.hasMarks() && this.hasMarker( assessment ) ) { + this.setHasMarkers( true ); + + result.setMarker( this.getMarker( assessment, paper, researcher ) ); + } + } catch ( assessmentError ) { + showTrace( assessmentError ); + + result = new AssessmentResult(); + + result.setScore( -1 ); + result.setText( sprintf( + /* translators: %1$s expands to the name of the assessment. */ + __( "An error occurred in the '%1$s' assessment", "wordpress-seo" ), + assessment.identifier, + assessmentError + ) ); } - } catch ( assessmentError ) { - showTrace( assessmentError ); - - result = new AssessmentResult(); - - result.setScore( -1 ); - result.setText( sprintf( - /* translators: %1$s expands to the name of the assessment. */ - __( "An error occurred in the '%1$s' assessment", "wordpress-seo" ), - assessment.identifier, - assessmentError - ) ); + return result; } - return result; -}; -/** - * Filters out all assessment results that have no score and no text. - * - * @returns {Array} The array with all the valid assessments. - */ -Assessor.prototype.getValidResults = function() { - return filter( this.results, function( result ) { - return this.isValidResult( result ); - }.bind( this ) ); -}; - -/** - * Returns if an assessmentResult is valid. - * - * @param {object} assessmentResult The assessmentResult to validate. - * @returns {boolean} whether or not the result is valid. - */ -Assessor.prototype.isValidResult = function( assessmentResult ) { - return assessmentResult.hasScore() && assessmentResult.hasText(); -}; + /** + * Filters out all assessment results that have no score and no text. + * + * @returns {Array} The array with all the valid assessments. + */ + getValidResults() { + return filter( this.results, function( result ) { + return this.isValidResult( result ); + }.bind( this ) ); + } -/** - * Returns the overall score. Calculates the total score by adding all scores and dividing these - * by the number of results times the ScoreRating. - * - * @returns {number} The overall score. - */ -Assessor.prototype.calculateOverallScore = function() { - const results = this.getValidResults(); + /** + * Returns if an assessmentResult is valid. + * + * @param {object} assessmentResult The assessmentResult to validate. + * @returns {boolean} whether or not the result is valid. + */ + isValidResult( assessmentResult ) { + return assessmentResult.hasScore() && assessmentResult.hasText(); + } - const totalScore = results.reduce( ( total, assessmentResult ) => total + assessmentResult.getScore(), 0 ); + /** + * Returns the overall score. Calculates the total score by adding all scores and dividing these + * by the number of results times the ScoreRating. + * + * @returns {number} The overall score. + */ + calculateOverallScore() { + const results = this.getValidResults(); - return Math.round( totalScore / ( results.length * ScoreRating ) * 100 ) || 0; -}; + const totalScore = results.reduce( ( total, assessmentResult ) => total + assessmentResult.getScore(), 0 ); -/** - * Register an assessment to add it to the internal assessments object. - * - * @param {string} name The name of the assessment. - * @param {object} assessment The object containing function to run as an assessment and it's requirements. - * @returns {boolean} Whether registering the assessment was successful. - * @private - */ -Assessor.prototype.addAssessment = function( name, assessment ) { - if ( ! assessment.hasOwnProperty( "identifier" ) ) { - assessment.identifier = name; - } - // If the assessor already has the same assessment, remove it and replace it with the new assessment with the same identifier. - if ( this.getAssessment( assessment.identifier ) ) { - this.removeAssessment( assessment.identifier ); + return Math.round( totalScore / ( results.length * ScoreRating ) * 100 ) || 0; } - this._assessments.push( assessment ); - return true; -}; + /** + * Register an assessment to add it to the internal assessments object. + * + * @param {string} name The name of the assessment. + * @param {object} assessment The object containing function to run as an assessment and it's requirements. + * @returns {boolean} Whether registering the assessment was successful. + * @private + */ + addAssessment( name, assessment ) { + if ( ! assessment.hasOwnProperty( "identifier" ) ) { + assessment.identifier = name; + } + // If the assessor already has the same assessment, remove it and replace it with the new assessment with the same identifier. + if ( this.getAssessment( assessment.identifier ) ) { + this.removeAssessment( assessment.identifier ); + } -/** - * Remove a specific Assessment from the list of Assessments. - * - * @param {string} name The Assessment to remove from the list of assessments. - * @returns {void} - */ -Assessor.prototype.removeAssessment = function( name ) { - const toDelete = findIndex( this._assessments, function( assessment ) { - return assessment.hasOwnProperty( "identifier" ) && name === assessment.identifier; - } ); + this._assessments.push( assessment ); + return true; + } - if ( -1 !== toDelete ) { - this._assessments.splice( toDelete, 1 ); + /** + * Remove a specific Assessment from the list of Assessments. + * + * @param {string} name The Assessment to remove from the list of assessments. + * @returns {void} + */ + removeAssessment( name ) { + const toDelete = findIndex( this._assessments, function( assessment ) { + return assessment.hasOwnProperty( "identifier" ) && name === assessment.identifier; + } ); + + if ( -1 !== toDelete ) { + this._assessments.splice( toDelete, 1 ); + } } -}; -/** - * Returns an assessment by identifier - * - * @param {string} identifier The identifier of the assessment. - * @returns {undefined|Assessment} The object if found, otherwise undefined. - */ -Assessor.prototype.getAssessment = function( identifier ) { - return find( this._assessments, function( assessment ) { - return assessment.hasOwnProperty( "identifier" ) && identifier === assessment.identifier; - } ); -}; + /** + * Returns an assessment by identifier + * + * @param {string} identifier The identifier of the assessment. + * @returns {undefined|Assessment} The object if found, otherwise undefined. + */ + getAssessment( identifier ) { + return find( this._assessments, function( assessment ) { + return assessment.hasOwnProperty( "identifier" ) && identifier === assessment.identifier; + } ); + } -/** - * Checks which of the available assessments are applicable and returns an array with applicable assessments. - * - * @returns {Array} The array with applicable assessments. - */ -Assessor.prototype.getApplicableAssessments = function() { - const availableAssessments = this.getAvailableAssessments(); - return filter( - availableAssessments, - function( availableAssessment ) { - return this.isApplicable( availableAssessment, this.getPaper(), this._researcher ); - }.bind( this ) - ); -}; + /** + * Checks which of the available assessments are applicable and returns an array with applicable assessments. + * + * @returns {Array} The array with applicable assessments. + */ + getApplicableAssessments() { + const availableAssessments = this.getAvailableAssessments(); + return filter( + availableAssessments, + function( availableAssessment ) { + return this.isApplicable( availableAssessment, this.getPaper(), this._researcher ); + }.bind( this ) + ); + } +} export default Assessor; diff --git a/packages/yoastseo/src/scoring/contentAssessor.js b/packages/yoastseo/src/scoring/contentAssessor.js index 3c93b86266d..70925200cd8 100644 --- a/packages/yoastseo/src/scoring/contentAssessor.js +++ b/packages/yoastseo/src/scoring/contentAssessor.js @@ -1,5 +1,4 @@ -import { inherits } from "util"; - +import { map, sum } from "lodash-es"; import Assessor from "./assessor.js"; import ParagraphTooLong from "./assessments/readability/ParagraphTooLongAssessment.js"; import SentenceLengthInText from "./assessments/readability/SentenceLengthInTextAssessment.js"; @@ -8,168 +7,156 @@ import TransitionWords from "./assessments/readability/TransitionWordsAssessment import PassiveVoice from "./assessments/readability/PassiveVoiceAssessment.js"; import SentenceBeginnings from "./assessments/readability/SentenceBeginningsAssessment.js"; import TextPresence from "./assessments/readability/TextPresenceAssessment.js"; - -/* - Temporarily disabled: - var sentenceLengthInDescription = require( "./assessments/sentenceLengthInDescriptionAssessment.js" ); - */ - import scoreToRating from "./interpreters/scoreToRating"; -import { map, sum } from "lodash-es"; - -/** - * Creates the Assessor - * - * @param {object} researcher The researcher to use for the analysis. - * @param {Object} options The options for this assessor. - * @param {Object} options.marker The marker to pass the list of marks to. - * - * @constructor - */ -const ContentAssessor = function( researcher, options = {} ) { - Assessor.call( this, researcher, options ); - this.type = "contentAssessor"; - this._assessments = [ - new SubheadingDistributionTooLong(), - new ParagraphTooLong(), - new SentenceLengthInText(), - new TransitionWords(), - new PassiveVoice(), - new TextPresence(), - new SentenceBeginnings(), - // Temporarily disabled: wordComplexity, - ]; -}; - -inherits( ContentAssessor, Assessor ); - /** - * Calculates the weighted rating for languages that have all assessments based on a given rating. - * - * @param {number} rating The rating to be weighted. - * @returns {number} The weighted rating. + * The ContentAssessor class is used for the readability analysis. */ -ContentAssessor.prototype.calculatePenaltyPointsFullSupport = function( rating ) { - switch ( rating ) { - case "bad": - return 3; - case "ok": - return 2; - default: - case "good": - return 0; +export default class ContentAssessor extends Assessor { + /** + * Creates a new ContentAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "contentAssessor"; + + this._assessments = [ + new SubheadingDistributionTooLong(), + new ParagraphTooLong(), + new SentenceLengthInText(), + new TransitionWords(), + new PassiveVoice(), + new TextPresence(), + new SentenceBeginnings(), + ]; } -}; -/** - * Calculates the weighted rating for languages that don't have all assessments based on a given rating. - * - * @param {number} rating The rating to be weighted. - * @returns {number} The weighted rating. - */ -ContentAssessor.prototype.calculatePenaltyPointsPartialSupport = function( rating ) { - switch ( rating ) { - case "bad": - return 4; - case "ok": - return 2; - default: - case "good": - return 0; + /** + * Calculates the weighted rating for languages that have all assessments based on a given rating. + * + * @param {string} rating The rating to be weighted. + * @returns {number} The weighted rating. + */ + calculatePenaltyPointsFullSupport( rating ) { + switch ( rating ) { + case "bad": + return 3; + case "ok": + return 2; + default: + case "good": + return 0; + } } -}; -/** - * Determines whether a language is fully supported. If a language supports 8 content assessments - * it is fully supported - * - * @returns {boolean} True if fully supported. - */ -ContentAssessor.prototype._allAssessmentsSupported = function() { - const numberOfAssessments = this._assessments.length; - const applicableAssessments = this.getApplicableAssessments(); - return applicableAssessments.length === numberOfAssessments; -}; + /** + * Calculates the weighted rating for languages that don't have all assessments based on a given rating. + * + * @param {string} rating The rating to be weighted. + * @returns {number} The weighted rating. + */ + calculatePenaltyPointsPartialSupport( rating ) { + switch ( rating ) { + case "bad": + return 4; + case "ok": + return 2; + default: + case "good": + return 0; + } + } -/** - * Calculates the penalty points based on the assessment results. - * - * @returns {number} The total penalty points for the results. - */ -ContentAssessor.prototype.calculatePenaltyPoints = function() { - const results = this.getValidResults(); + /** + * Determines whether a language is fully supported. If a language supports 8 content assessments + * it is fully supported + * + * @returns {boolean} True if fully supported. + */ + _allAssessmentsSupported() { + const numberOfAssessments = this._assessments.length; + const applicableAssessments = this.getApplicableAssessments(); + return applicableAssessments.length === numberOfAssessments; + } - const penaltyPoints = map( results, function( result ) { - const rating = scoreToRating( result.getScore() ); + /** + * Calculates the penalty points based on the assessment results. + * + * @returns {number} The total penalty points for the results. + */ + calculatePenaltyPoints() { + const results = this.getValidResults(); - if ( this._allAssessmentsSupported() ) { - return this.calculatePenaltyPointsFullSupport( rating ); - } + const penaltyPoints = map( results, function( result ) { + const rating = scoreToRating( result.getScore() ); - return this.calculatePenaltyPointsPartialSupport( rating ); - }.bind( this ) ); + if ( this._allAssessmentsSupported() ) { + return this.calculatePenaltyPointsFullSupport( rating ); + } - return sum( penaltyPoints ); -}; + return this.calculatePenaltyPointsPartialSupport( rating ); + }.bind( this ) ); -/** - * Rates the penalty points - * - * @param {number} totalPenaltyPoints The amount of penalty points. - * @returns {number} The score based on the amount of penalty points. - * - * @private - */ -ContentAssessor.prototype._ratePenaltyPoints = function( totalPenaltyPoints ) { - if ( this.getValidResults().length === 1 ) { - // If we have only 1 result, we only have a "no content" result - return 30; + return sum( penaltyPoints ); } - if ( this._allAssessmentsSupported() ) { - // Determine the total score based on the total penalty points. - if ( totalPenaltyPoints > 6 ) { - // A red indicator. - return 30; - } - - if ( totalPenaltyPoints > 4 ) { - // An orange indicator. - return 60; - } - } else { - if ( totalPenaltyPoints > 4 ) { - // A red indicator. + /** + * Rates the penalty points + * + * @param {number} totalPenaltyPoints The amount of penalty points. + * @returns {number} The score based on the amount of penalty points. + * + * @private + */ + _ratePenaltyPoints( totalPenaltyPoints ) { + if ( this.getValidResults().length === 1 ) { + // If we have only 1 result, we only have a "no content" result return 30; } - if ( totalPenaltyPoints > 2 ) { - // An orange indicator. - return 60; + if ( this._allAssessmentsSupported() ) { + // Determine the total score based on the total penalty points. + if ( totalPenaltyPoints > 6 ) { + // A red indicator. + return 30; + } + + if ( totalPenaltyPoints > 4 ) { + // An orange indicator. + return 60; + } + } else { + if ( totalPenaltyPoints > 4 ) { + // A red indicator. + return 30; + } + + if ( totalPenaltyPoints > 2 ) { + // An orange indicator. + return 60; + } } + // A green indicator. + return 90; } - // A green indicator. - return 90; -}; -/** - * Calculates the overall score based on the assessment results. - * - * @returns {number} The overall score. - */ -ContentAssessor.prototype.calculateOverallScore = function() { - const results = this.getValidResults(); - - // If you have no content, you have a red indicator. - if ( results.length === 0 ) { - return 30; - } + /** + * Calculates the overall score based on the assessment results. + * + * @returns {number} The overall score. + */ + calculateOverallScore() { + const results = this.getValidResults(); - const totalPenaltyPoints = this.calculatePenaltyPoints(); - - return this._ratePenaltyPoints( totalPenaltyPoints ); -}; + // If you have no content, you have a red indicator. + if ( results.length === 0 ) { + return 30; + } -export default ContentAssessor; + const totalPenaltyPoints = this.calculatePenaltyPoints(); + return this._ratePenaltyPoints( totalPenaltyPoints ); + } +} diff --git a/packages/yoastseo/src/scoring/cornerstone/contentAssessor.js b/packages/yoastseo/src/scoring/cornerstone/contentAssessor.js index c090ecb05d3..5911f39f77a 100644 --- a/packages/yoastseo/src/scoring/cornerstone/contentAssessor.js +++ b/packages/yoastseo/src/scoring/cornerstone/contentAssessor.js @@ -1,5 +1,3 @@ -import { inherits } from "util"; -import Assessor from "../assessor.js"; import ContentAssessor from "../contentAssessor"; import ParagraphTooLong from "../assessments/readability/ParagraphTooLongAssessment.js"; import SentenceLengthInText from "../assessments/readability/SentenceLengthInTextAssessment.js"; @@ -9,50 +7,38 @@ import PassiveVoice from "../assessments/readability/PassiveVoiceAssessment.js"; import SentenceBeginnings from "../assessments/readability/SentenceBeginningsAssessment.js"; import TextPresence from "../assessments/readability/TextPresenceAssessment.js"; -/* - Temporarily disabled: - - var sentenceLengthInDescription = require( "./assessments/readability/sentenceLengthInDescriptionAssessment.js" ); - */ - /** - * Creates the Assessor - * - * @param {object} researcher The researcher used for the analysis. - * @param {Object} options The options for this assessor. - * @param {Object} options.marker The marker to pass the list of marks to. - * - * @constructor + * The CornerStoneContentAssessor class is used for the readability analysis on cornerstone content. */ -const CornerStoneContentAssessor = function( researcher, options = {} ) { - Assessor.call( this, researcher, options ); - this.type = "cornerstoneContentAssessor"; - - this._assessments = [ - new SubheadingDistributionTooLong( { - parameters: { - slightlyTooMany: 250, - farTooMany: 300, - recommendedMaximumLength: 250, - }, - applicableIfTextLongerThan: 250, - cornerstoneContent: true, - } ), - new ParagraphTooLong(), - new SentenceLengthInText( { - slightlyTooMany: 20, - farTooMany: 25, - }, true ), - new TransitionWords(), - new PassiveVoice(), - new TextPresence(), - new SentenceBeginnings(), - // Temporarily disabled: wordComplexity, - ]; -}; - -inherits( CornerStoneContentAssessor, ContentAssessor ); - - -export default CornerStoneContentAssessor; - +export default class CornerStoneContentAssessor extends ContentAssessor { + /** + * Creates a new CornerStoneContentAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "cornerstoneContentAssessor"; + + this._assessments = [ + new SubheadingDistributionTooLong( { + parameters: { + slightlyTooMany: 250, + farTooMany: 300, + recommendedMaximumLength: 250, + }, + applicableIfTextLongerThan: 250, + cornerstoneContent: true, + } ), + new ParagraphTooLong(), + new SentenceLengthInText( { + slightlyTooMany: 20, + farTooMany: 25, + }, true ), + new TransitionWords(), + new PassiveVoice(), + new TextPresence(), + new SentenceBeginnings(), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/cornerstone/relatedKeywordAssessor.js b/packages/yoastseo/src/scoring/cornerstone/relatedKeywordAssessor.js index ca825e19bae..32010063806 100644 --- a/packages/yoastseo/src/scoring/cornerstone/relatedKeywordAssessor.js +++ b/packages/yoastseo/src/scoring/cornerstone/relatedKeywordAssessor.js @@ -1,6 +1,4 @@ -import { inherits } from "util"; import Assessor from "../assessor.js"; - import IntroductionKeyword from "../assessments/seo/IntroductionKeywordAssessment.js"; import KeyphraseLength from "../assessments/seo/KeyphraseLengthAssessment.js"; import KeyphraseDensityAssessment from "../assessments/seo/KeywordDensityAssessment.js"; @@ -10,35 +8,32 @@ import FunctionWordsInKeyphrase from "../assessments/seo/FunctionWordsInKeyphras import ImageKeyphrase from "../assessments/seo/KeyphraseInImageTextAssessment"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher used for the analysis. - * @param {Object?} options The options for this assessor. - * @param {Function} options.marker The marker to pass the list of marks to. - * - * @constructor + * The relatedKeywordAssessor class is used for the related keyword analysis for cornerstone content. */ -const relatedKeywordAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "cornerstoneRelatedKeywordAssessor"; - - this._assessments = [ - new IntroductionKeyword(), - new KeyphraseLength( { isRelatedKeyphrase: true } ), - new KeyphraseDensityAssessment(), - new MetaDescriptionKeyword(), - new TextCompetingLinks(), - new FunctionWordsInKeyphrase(), - new ImageKeyphrase( { - scores: { - withAltNonKeyword: 3, - withAlt: 3, - noAlt: 3, - }, - } ), - ]; -}; - -inherits( relatedKeywordAssessor, Assessor ); +export default class relatedKeywordAssessor extends Assessor { + /** + * Creates a new relatedKeywordAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "cornerstoneRelatedKeywordAssessor"; -export default relatedKeywordAssessor; + this._assessments = [ + new IntroductionKeyword(), + new KeyphraseLength( { isRelatedKeyphrase: true } ), + new KeyphraseDensityAssessment(), + new MetaDescriptionKeyword(), + new TextCompetingLinks(), + new FunctionWordsInKeyphrase(), + new ImageKeyphrase( { + scores: { + withAltNonKeyword: 3, + withAlt: 3, + noAlt: 3, + }, + } ), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/cornerstone/seoAssessor.js b/packages/yoastseo/src/scoring/cornerstone/seoAssessor.js index d816dcd6c51..44392ed661a 100644 --- a/packages/yoastseo/src/scoring/cornerstone/seoAssessor.js +++ b/packages/yoastseo/src/scoring/cornerstone/seoAssessor.js @@ -1,5 +1,4 @@ -import { inherits } from "util"; - +import SEOAssessor from "../seoAssessor"; import IntroductionKeywordAssessment from "../assessments/seo/IntroductionKeywordAssessment"; import KeyphraseLengthAssessment from "../assessments/seo/KeyphraseLengthAssessment"; import KeyphraseDensityAssessment from "../assessments/seo/KeywordDensityAssessment"; @@ -8,8 +7,6 @@ import TextCompetingLinksAssessment from "../assessments/seo/TextCompetingLinksA import InternalLinksAssessment from "../assessments/seo/InternalLinksAssessment"; import KeyphraseInSEOTitleAssessment from "../assessments/seo/KeyphraseInSEOTitleAssessment"; import SlugKeywordAssessment from "../assessments/seo/UrlKeywordAssessment"; -import Assessor from "../assessor"; -import SEOAssessor from "../seoAssessor"; import MetaDescriptionLength from "../assessments/seo/MetaDescriptionLengthAssessment"; import SubheadingsKeyword from "../assessments/seo/SubHeadingsKeywordAssessment"; import ImageKeyphrase from "../assessments/seo/KeyphraseInImageTextAssessment"; @@ -21,78 +18,75 @@ import FunctionWordsInKeyphrase from "../assessments/seo/FunctionWordsInKeyphras import SingleH1Assessment from "../assessments/seo/SingleH1Assessment"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher used for the analysis. - * @param {Object?} options The options for this assessor. - * @param {Function} options.marker The marker to pass the list of marks to. - * - * @constructor + * The CornerstoneSEOAssessor class is used for the SEO analysis for cornerstone content. */ -const CornerstoneSEOAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "cornerstoneSEOAssessor"; - - this._assessments = [ - new IntroductionKeywordAssessment(), - new KeyphraseLengthAssessment(), - new KeyphraseDensityAssessment(), - new MetaDescriptionKeywordAssessment(), - new MetaDescriptionLength( { - scores: { - tooLong: 3, - tooShort: 3, - }, - } ), - new SubheadingsKeyword(), - new TextCompetingLinksAssessment(), - new ImageKeyphrase( { - scores: { - withAltNonKeyword: 3, - withAlt: 3, - noAlt: 3, - }, - } ), - new ImageCount(), - new TextLength( { - recommendedMinimum: 900, - slightlyBelowMinimum: 400, - belowMinimum: 300, - - scores: { - belowMinimum: -20, - farBelowMinimum: -20, - }, +export default class CornerstoneSEOAssessor extends SEOAssessor { + /** + * Creates a new CornerstoneSEOAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "cornerstoneSEOAssessor"; - cornerstoneContent: true, - } ), - new OutboundLinks( { - scores: { - noLinks: 3, - }, - } ), - new KeyphraseInSEOTitleAssessment(), - new InternalLinksAssessment(), - new TitleWidth( - { + this._assessments = [ + new IntroductionKeywordAssessment(), + new KeyphraseLengthAssessment(), + new KeyphraseDensityAssessment(), + new MetaDescriptionKeywordAssessment(), + new MetaDescriptionLength( { scores: { - widthTooShort: 9, + tooLong: 3, + tooShort: 3, }, - }, - true - ), - new SlugKeywordAssessment( - { + } ), + new SubheadingsKeyword(), + new TextCompetingLinksAssessment(), + new ImageKeyphrase( { scores: { - okay: 3, + withAltNonKeyword: 3, + withAlt: 3, + noAlt: 3, }, - } - ), - new FunctionWordsInKeyphrase(), - new SingleH1Assessment(), - ]; -}; + } ), + new ImageCount(), + new TextLength( { + recommendedMinimum: 900, + slightlyBelowMinimum: 400, + belowMinimum: 300, -inherits( CornerstoneSEOAssessor, SEOAssessor ); + scores: { + belowMinimum: -20, + farBelowMinimum: -20, + }, -export default CornerstoneSEOAssessor; + cornerstoneContent: true, + } ), + new OutboundLinks( { + scores: { + noLinks: 3, + }, + } ), + new KeyphraseInSEOTitleAssessment(), + new InternalLinksAssessment(), + new TitleWidth( + { + scores: { + widthTooShort: 9, + }, + }, + true + ), + new SlugKeywordAssessment( + { + scores: { + okay: 3, + }, + } + ), + new FunctionWordsInKeyphrase(), + new SingleH1Assessment(), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/inclusiveLanguageAssessor.js b/packages/yoastseo/src/scoring/inclusiveLanguageAssessor.js index 5338b69eac8..aa37c643e1b 100644 --- a/packages/yoastseo/src/scoring/inclusiveLanguageAssessor.js +++ b/packages/yoastseo/src/scoring/inclusiveLanguageAssessor.js @@ -13,14 +13,13 @@ const defaultOptions = { }; /** - * An assessor that assesses a paper for potentially non-inclusive language. + * The InclusiveLanguageAssessor assesses a paper for potentially non-inclusive language. */ -class InclusiveLanguageAssessor extends Assessor { +export default class InclusiveLanguageAssessor extends Assessor { /** - * Creates a new inclusive language assessor. - * - * @param {Researcher} researcher The researcher to use. - * @param {Object} [options] The assessor options. + * Creates a new InclusiveLanguageAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. */ constructor( researcher, options = {} ) { super( researcher, options ); @@ -63,5 +62,3 @@ class InclusiveLanguageAssessor extends Assessor { return 90; } } - -export default InclusiveLanguageAssessor; diff --git a/packages/yoastseo/src/scoring/relatedKeywordAssessor.js b/packages/yoastseo/src/scoring/relatedKeywordAssessor.js index ddf792d9abf..6f2c21647cf 100644 --- a/packages/yoastseo/src/scoring/relatedKeywordAssessor.js +++ b/packages/yoastseo/src/scoring/relatedKeywordAssessor.js @@ -1,38 +1,33 @@ -import { inherits } from "util"; - import Assessor from "./assessor.js"; import IntroductionKeyword from "./assessments/seo/IntroductionKeywordAssessment.js"; import KeyphraseLength from "./assessments/seo/KeyphraseLengthAssessment.js"; import KeyphraseDensityAssessment from "./assessments/seo/KeywordDensityAssessment.js"; import MetaDescriptionKeyword from "./assessments/seo/MetaDescriptionKeywordAssessment.js"; -import ImageKeyphrase from "./assessments/seo/KeyphraseInImageTextAssessment"; import TextCompetingLinks from "./assessments/seo/TextCompetingLinksAssessment.js"; import FunctionWordsInKeyphrase from "./assessments/seo/FunctionWordsInKeyphraseAssessment"; +import ImageKeyphrase from "./assessments/seo/KeyphraseInImageTextAssessment"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher to use for the analysis. - * @param {Object?} options The options for this assessor. - * @param {Function} options.marker The marker to pass the list of marks to. - * - * @constructor + * The relatedKeywordAssessor class is used for the related keyword analysis. */ -const relatedKeywordAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "relatedKeywordAssessor"; - - this._assessments = [ - new IntroductionKeyword(), - new KeyphraseLength( { isRelatedKeyphrase: true } ), - new KeyphraseDensityAssessment(), - new MetaDescriptionKeyword(), - new TextCompetingLinks(), - new FunctionWordsInKeyphrase(), - new ImageKeyphrase(), - ]; -}; - -inherits( relatedKeywordAssessor, Assessor ); +export default class relatedKeywordAssessor extends Assessor { + /** + * Creates a new relatedKeywordAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "relatedKeywordAssessor"; -export default relatedKeywordAssessor; + this._assessments = [ + new IntroductionKeyword(), + new KeyphraseLength( { isRelatedKeyphrase: true } ), + new KeyphraseDensityAssessment(), + new MetaDescriptionKeyword(), + new TextCompetingLinks(), + new FunctionWordsInKeyphrase(), + new ImageKeyphrase(), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/relatedKeywordTaxonomyAssessor.js b/packages/yoastseo/src/scoring/relatedKeywordTaxonomyAssessor.js index bb75942b814..854d0d6e838 100644 --- a/packages/yoastseo/src/scoring/relatedKeywordTaxonomyAssessor.js +++ b/packages/yoastseo/src/scoring/relatedKeywordTaxonomyAssessor.js @@ -1,34 +1,30 @@ -import { inherits } from "util"; - +import Assessor from "./assessor"; import IntroductionKeywordAssessment from "./assessments/seo/IntroductionKeywordAssessment"; import KeyphraseLengthAssessment from "./assessments/seo/KeyphraseLengthAssessment"; import KeyphraseDensityAssessment from "./assessments/seo/KeywordDensityAssessment"; import MetaDescriptionKeywordAssessment from "./assessments/seo/MetaDescriptionKeywordAssessment"; -import Assessor from "./assessor"; import FunctionWordsInKeyphrase from "./assessments/seo/FunctionWordsInKeyphraseAssessment"; /** - * Creates the Assessor used for taxonomy pages. - * - * @param {Researcher} researcher The researcher to use for the analysis. - * @param {Object?} options The options for this assessor. - * - * @constructor + * The RelatedKeywordTaxonomyAssessor class is used for the related keyword analysis on terms. */ -const RelatedKeywordTaxonomyAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "relatedKeywordsTaxonomyAssessor"; - - this._assessments = [ - new IntroductionKeywordAssessment(), - new KeyphraseLengthAssessment( { isRelatedKeyphrase: true } ), - new KeyphraseDensityAssessment(), - new MetaDescriptionKeywordAssessment(), - // Text Images assessment here. - new FunctionWordsInKeyphrase(), - ]; -}; - -inherits( RelatedKeywordTaxonomyAssessor, Assessor ); +export default class RelatedKeywordTaxonomyAssessor extends Assessor { + /** + * Creates a new RelatedKeywordTaxonomyAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "relatedKeywordsTaxonomyAssessor"; -export default RelatedKeywordTaxonomyAssessor; + this._assessments = [ + new IntroductionKeywordAssessment(), + new KeyphraseLengthAssessment( { isRelatedKeyphrase: true } ), + new KeyphraseDensityAssessment(), + new MetaDescriptionKeywordAssessment(), + // Text Images assessment here. + new FunctionWordsInKeyphrase(), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/seoAssessor.js b/packages/yoastseo/src/scoring/seoAssessor.js index ae228ea591d..aaa0a1bdb44 100644 --- a/packages/yoastseo/src/scoring/seoAssessor.js +++ b/packages/yoastseo/src/scoring/seoAssessor.js @@ -1,5 +1,4 @@ -import { inherits } from "util"; - +import Assessor from "./assessor"; import IntroductionKeywordAssessment from "./assessments/seo/IntroductionKeywordAssessment"; import KeyphraseLengthAssessment from "./assessments/seo/KeyphraseLengthAssessment"; import KeyphraseDensityAssessment from "./assessments/seo/KeywordDensityAssessment"; @@ -8,7 +7,6 @@ import TextCompetingLinksAssessment from "./assessments/seo/TextCompetingLinksAs import InternalLinksAssessment from "./assessments/seo/InternalLinksAssessment"; import KeyphraseInSEOTitleAssessment from "./assessments/seo/KeyphraseInSEOTitleAssessment"; import SlugKeywordAssessment from "./assessments/seo/UrlKeywordAssessment"; -import Assessor from "./assessor"; import MetaDescriptionLength from "./assessments/seo/MetaDescriptionLengthAssessment"; import SubheadingsKeyword from "./assessments/seo/SubHeadingsKeywordAssessment"; import ImageKeyphrase from "./assessments/seo/KeyphraseInImageTextAssessment"; @@ -20,43 +18,40 @@ import FunctionWordsInKeyphrase from "./assessments/seo/FunctionWordsInKeyphrase import SingleH1Assessment from "./assessments/seo/SingleH1Assessment"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher to use for the analysis. - * @param {Object?} options The options for this assessor. - * @param {Function} options.marker The marker to pass the list of marks to. - * - * @constructor + * The SEOAssessor class is used for the general SEO analysis. */ -const SEOAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "SEOAssessor"; - - this._assessments = [ - new IntroductionKeywordAssessment(), - new KeyphraseLengthAssessment(), - new KeyphraseDensityAssessment(), - new MetaDescriptionKeywordAssessment(), - new MetaDescriptionLength(), - new SubheadingsKeyword(), - new TextCompetingLinksAssessment(), - new ImageKeyphrase(), - new ImageCount(), - new TextLength(), - new OutboundLinks(), - new KeyphraseInSEOTitleAssessment(), - new InternalLinksAssessment(), - new TitleWidth( { - scores: { - widthTooShort: 9, - }, - }, true ), - new SlugKeywordAssessment(), - new FunctionWordsInKeyphrase(), - new SingleH1Assessment(), - ]; -}; - -inherits( SEOAssessor, Assessor ); +export default class SEOAssessor extends Assessor { + /** + * Creates a new SEOAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "SEOAssessor"; -export default SEOAssessor; + this._assessments = [ + new IntroductionKeywordAssessment(), + new KeyphraseLengthAssessment(), + new KeyphraseDensityAssessment(), + new MetaDescriptionKeywordAssessment(), + new MetaDescriptionLength(), + new SubheadingsKeyword(), + new TextCompetingLinksAssessment(), + new ImageKeyphrase(), + new ImageCount(), + new TextLength(), + new OutboundLinks(), + new KeyphraseInSEOTitleAssessment(), + new InternalLinksAssessment(), + new TitleWidth( { + scores: { + widthTooShort: 9, + }, + }, true ), + new SlugKeywordAssessment(), + new FunctionWordsInKeyphrase(), + new SingleH1Assessment(), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/storePostsAndPages/cornerstone/contentAssessor.js b/packages/yoastseo/src/scoring/storePostsAndPages/cornerstone/contentAssessor.js index 38a746c4beb..5d7188f87cb 100644 --- a/packages/yoastseo/src/scoring/storePostsAndPages/cornerstone/contentAssessor.js +++ b/packages/yoastseo/src/scoring/storePostsAndPages/cornerstone/contentAssessor.js @@ -1,6 +1,4 @@ -import { inherits } from "util"; - -import { Assessor, ContentAssessor, assessments, helpers } from "yoastseo"; +import { ContentAssessor, assessments, helpers } from "yoastseo"; const { createAnchorOpeningTag } = helpers; const { @@ -27,53 +25,59 @@ const { * * @constructor */ -const StorePostsAndPagesCornerstoneContentAssessor = function( researcher, options = {} ) { - Assessor.call( this, researcher, options ); - this.type = "storePostsAndPagesCornerstoneContentAssessor"; - - this._assessments = [ +class StorePostsAndPagesCornerstoneContentAssessor extends ContentAssessor { + /** + * Creates a new assessor. + * + * @param {Researcher} researcher The researcher to use. + * @param {Object} options The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); - new SubheadingDistributionTooLongAssessment( { - parameters: { - slightlyTooMany: 250, - farTooMany: 300, - recommendedMaximumLength: 250, - }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify68" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify69" ), - cornerstoneContent: true, - } ), - new ParagraphTooLongAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify66" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify67" ), - } ), - new SentenceLengthInTextAssessment( { - slightlyTooMany: 20, - farTooMany: 25, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify48" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify49" ), - }, true ), - new TransitionWordsAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify44" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify45" ), - } ), - new PassiveVoiceAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify42" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify43" ), - } ), - new TextPresenceAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify56" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify57" ), - } ), - new SentenceBeginningsAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify5" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify65" ), - } ), - ]; -}; + this.type = "storePostsAndPagesCornerstoneContentAssessor"; -inherits( StorePostsAndPagesCornerstoneContentAssessor, ContentAssessor ); + this._assessments = [ + new SubheadingDistributionTooLongAssessment( { + parameters: { + slightlyTooMany: 250, + farTooMany: 300, + recommendedMaximumLength: 250, + }, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify68" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify69" ), + cornerstoneContent: true, + } ), + new ParagraphTooLongAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify66" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify67" ), + } ), + new SentenceLengthInTextAssessment( { + slightlyTooMany: 20, + farTooMany: 25, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify48" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify49" ), + }, true ), + new TransitionWordsAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify44" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify45" ), + } ), + new PassiveVoiceAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify42" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify43" ), + } ), + new TextPresenceAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify56" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify57" ), + } ), + new SentenceBeginningsAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify5" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify65" ), + } ), + ]; + } +} export default StorePostsAndPagesCornerstoneContentAssessor; diff --git a/packages/yoastseo/src/scoring/taxonomyAssessor.js b/packages/yoastseo/src/scoring/taxonomyAssessor.js index d682e36a487..ec184305850 100644 --- a/packages/yoastseo/src/scoring/taxonomyAssessor.js +++ b/packages/yoastseo/src/scoring/taxonomyAssessor.js @@ -1,10 +1,10 @@ +import Assessor from "./assessor"; import IntroductionKeywordAssessment from "./assessments/seo/IntroductionKeywordAssessment"; import KeyphraseLengthAssessment from "./assessments/seo/KeyphraseLengthAssessment"; import KeyphraseDensityAssessment from "./assessments/seo/KeywordDensityAssessment"; import MetaDescriptionKeywordAssessment from "./assessments/seo/MetaDescriptionKeywordAssessment"; import KeyphraseInSEOTitleAssessment from "./assessments/seo/KeyphraseInSEOTitleAssessment"; import SlugKeywordAssessment from "./assessments/seo/UrlKeywordAssessment"; -import Assessor from "./assessor"; import MetaDescriptionLengthAssessment from "./assessments/seo/MetaDescriptionLengthAssessment"; import TextLengthAssessment from "./assessments/seo/TextLengthAssessment"; import PageTitleWidthAssessment from "./assessments/seo/PageTitleWidthAssessment"; @@ -17,7 +17,7 @@ import { createAnchorOpeningTag } from "../helpers"; * * @returns {TextLengthAssessment} The text length assessment (with taxonomy configuration) to use. */ -export const getTextLengthAssessment = function() { +export const getTextLengthAssessment = () => { // Export so it can be used in tests. return new TextLengthAssessment( { recommendedMinimum: 30, @@ -30,18 +30,16 @@ export const getTextLengthAssessment = function() { }; /** - * Creates the Assessor used for taxonomy pages. + * The TaxonomyAssessor is used for the assessment of terms. */ -class TaxonomyAssessor extends Assessor { +export default class TaxonomyAssessor extends Assessor { /** - * Creates a new taxonomy assessor. - * + * Creates a new TaxonomyAssessor instance. * @param {Researcher} researcher The researcher to use. - * @param {Object} options The assessor options. + * @param {Object} [options] The assessor options. */ constructor( researcher, options ) { super( researcher, options ); - this.type = "taxonomyAssessor"; this._assessments = [ @@ -65,5 +63,3 @@ class TaxonomyAssessor extends Assessor { ]; } } - -export default TaxonomyAssessor; From 995dc7ac4639b2e734a499452d494915f69b2a71 Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Wed, 24 Apr 2024 10:45:14 +0200 Subject: [PATCH 013/313] Alternative way of providing cornerstone values --- packages/yoastseo/src/scoring/assessor.js | 1 - .../src/scoring/cornerstone/seoAssessor.js | 93 +++++-------------- 2 files changed, 23 insertions(+), 71 deletions(-) diff --git a/packages/yoastseo/src/scoring/assessor.js b/packages/yoastseo/src/scoring/assessor.js index a912d0c28ac..e4cbfcd4625 100644 --- a/packages/yoastseo/src/scoring/assessor.js +++ b/packages/yoastseo/src/scoring/assessor.js @@ -241,7 +241,6 @@ class Assessor { * @param {string} name The name of the assessment. * @param {object} assessment The object containing function to run as an assessment and it's requirements. * @returns {boolean} Whether registering the assessment was successful. - * @private */ addAssessment( name, assessment ) { if ( ! assessment.hasOwnProperty( "identifier" ) ) { diff --git a/packages/yoastseo/src/scoring/cornerstone/seoAssessor.js b/packages/yoastseo/src/scoring/cornerstone/seoAssessor.js index 44392ed661a..c7ef48ed42c 100644 --- a/packages/yoastseo/src/scoring/cornerstone/seoAssessor.js +++ b/packages/yoastseo/src/scoring/cornerstone/seoAssessor.js @@ -1,21 +1,10 @@ import SEOAssessor from "../seoAssessor"; -import IntroductionKeywordAssessment from "../assessments/seo/IntroductionKeywordAssessment"; -import KeyphraseLengthAssessment from "../assessments/seo/KeyphraseLengthAssessment"; -import KeyphraseDensityAssessment from "../assessments/seo/KeywordDensityAssessment"; -import MetaDescriptionKeywordAssessment from "../assessments/seo/MetaDescriptionKeywordAssessment"; -import TextCompetingLinksAssessment from "../assessments/seo/TextCompetingLinksAssessment"; -import InternalLinksAssessment from "../assessments/seo/InternalLinksAssessment"; -import KeyphraseInSEOTitleAssessment from "../assessments/seo/KeyphraseInSEOTitleAssessment"; -import SlugKeywordAssessment from "../assessments/seo/UrlKeywordAssessment"; import MetaDescriptionLength from "../assessments/seo/MetaDescriptionLengthAssessment"; -import SubheadingsKeyword from "../assessments/seo/SubHeadingsKeywordAssessment"; import ImageKeyphrase from "../assessments/seo/KeyphraseInImageTextAssessment"; -import ImageCount from "../assessments/seo/ImageCountAssessment"; import TextLength from "../assessments/seo/TextLengthAssessment"; import OutboundLinks from "../assessments/seo/OutboundLinksAssessment"; import TitleWidth from "../assessments/seo/PageTitleWidthAssessment"; -import FunctionWordsInKeyphrase from "../assessments/seo/FunctionWordsInKeyphraseAssessment"; -import SingleH1Assessment from "../assessments/seo/SingleH1Assessment"; +import SlugKeywordAssessment from "../assessments/seo/UrlKeywordAssessment"; /** * The CornerstoneSEOAssessor class is used for the SEO analysis for cornerstone content. @@ -30,63 +19,27 @@ export default class CornerstoneSEOAssessor extends SEOAssessor { super( researcher, options ); this.type = "cornerstoneSEOAssessor"; - this._assessments = [ - new IntroductionKeywordAssessment(), - new KeyphraseLengthAssessment(), - new KeyphraseDensityAssessment(), - new MetaDescriptionKeywordAssessment(), - new MetaDescriptionLength( { - scores: { - tooLong: 3, - tooShort: 3, - }, - } ), - new SubheadingsKeyword(), - new TextCompetingLinksAssessment(), - new ImageKeyphrase( { - scores: { - withAltNonKeyword: 3, - withAlt: 3, - noAlt: 3, - }, - } ), - new ImageCount(), - new TextLength( { - recommendedMinimum: 900, - slightlyBelowMinimum: 400, - belowMinimum: 300, - - scores: { - belowMinimum: -20, - farBelowMinimum: -20, - }, - - cornerstoneContent: true, - } ), - new OutboundLinks( { - scores: { - noLinks: 3, - }, - } ), - new KeyphraseInSEOTitleAssessment(), - new InternalLinksAssessment(), - new TitleWidth( - { - scores: { - widthTooShort: 9, - }, - }, - true - ), - new SlugKeywordAssessment( - { - scores: { - okay: 3, - }, - } - ), - new FunctionWordsInKeyphrase(), - new SingleH1Assessment(), - ]; + this.addAssessment( "metaDescriptionLength", new MetaDescriptionLength( { + scores: { tooLong: 3, tooShort: 3 }, + } ) ); + this.addAssessment( "imageKeyphrase", new ImageKeyphrase( { + scores: { withAltNonKeyword: 3, withAlt: 3, noAlt: 3 }, + } ) ); + this.addAssessment( "textLength", new TextLength( { + recommendedMinimum: 900, + slightlyBelowMinimum: 400, + belowMinimum: 300, + scores: { belowMinimum: -20, farBelowMinimum: -20 }, + cornerstoneContent: true, + } ) ); + this.addAssessment( "externalLinks", new OutboundLinks( { + scores: { noLinks: 3 }, + } ) ); + this.addAssessment( "titleWidth", new TitleWidth( { + scores: { widthTooShort: 9 }, + }, true ) ); + this.addAssessment( "slugKeyword", new SlugKeywordAssessment( { + scores: { okay: 3 }, + } ) ); } } From b368328856cca31686e598230a1c89f217b2b991 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Fri, 9 Aug 2024 14:48:15 +0200 Subject: [PATCH 014/313] Progress of reusing image extraction code. --- inc/class-wpseo-content-images.php | 82 ++++++++- src/builders/indexable-link-builder.php | 174 ++---------------- src/builders/indexable-post-builder.php | 2 + .../Application/Image_Content_Extractor.php | 166 +++++++++++++++++ 4 files changed, 258 insertions(+), 166 deletions(-) create mode 100644 src/images/Application/Image_Content_Extractor.php diff --git a/inc/class-wpseo-content-images.php b/inc/class-wpseo-content-images.php index 6218c004e09..aa06980e90d 100644 --- a/inc/class-wpseo-content-images.php +++ b/inc/class-wpseo-content-images.php @@ -30,18 +30,92 @@ public function get_images( $post_id, $post = null ) { * @return array An array of image URLs. */ public function get_images_from_content( $content ) { + + if ( ! is_string( $content ) ) { return []; } + $content_images2 = $this->gather_images_wp( $content); $content_images = $this->get_img_tags_from_content( $content ); - $images = array_map( [ $this, 'get_img_tag_source' ], $content_images ); - $images = array_filter( $images ); - $images = array_unique( $images ); - $images = array_values( $images ); // Reset the array keys. + + $images = array_map( [ $this, 'get_img_tag_source' ], $content_images ); + $images = array_filter( $images ); + $images = array_unique( $images ); + $images = array_values( $images ); // Reset the array keys. + var_dump( $content_images2 ); + var_dump( $images ); + die; + return $images; + } + /** + * Gathers all images from content with WP's WP_HTML_Tag_Processor() and returns them along with their IDs, if + * possible. + * + * @param string $content The content. + * + * @return int[] An associated array of image IDs, keyed by their URL. + */ + protected function gather_images_wp( $content ) { + $processor = new WP_HTML_Tag_Processor( $content ); + $images = []; + + $query = [ + 'tag_name' => 'img', + ]; + + /** + * Filter 'wpseo_image_attribute_containing_id' - Allows filtering what attribute will be used to extract image IDs from. + * + * Defaults to "class", which is where WP natively stores the image IDs, in a `wp-image-` format. + * + * @api string The attribute to be used to extract image IDs from. + */ + $attribute = \apply_filters( 'wpseo_image_attribute_containing_id', 'class' ); + + while ( $processor->next_tag( $query ) ) { + $src = \htmlentities( $processor->get_attribute( 'src' ), ( \ENT_QUOTES | \ENT_SUBSTITUTE | \ENT_HTML401 ), \get_bloginfo( 'charset' ) ); + $classes = $processor->get_attribute( $attribute ); + $id = $this->extract_id_of_classes( $classes ); + + $images[ $src ] = $id; + } return $images; } + /** + * Extracts image ID out of the image's classes. + * + * @param string $classes The classes assigned to the image. + * + * @return int The ID that's extracted from the classes. + */ + protected function extract_id_of_classes( $classes ) { + if ( ! $classes ) { + return 0; + } + + /** + * Filter 'wpseo_extract_id_pattern' - Allows filtering the regex patern to be used to extract image IDs from class/attribute names. + * + * Defaults to the pattern that extracts image IDs from core's `wp-image-` native format in image classes. + * + * @api string The regex pattern to be used to extract image IDs from class names. Empty string if the whole class/attribute should be returned. + */ + $pattern = \apply_filters( 'wpseo_extract_id_pattern', '/(?seo_links_repository = $seo_links_repository; $this->url_helper = $url_helper; $this->post_helper = $post_helper; $this->options_helper = $options_helper; $this->indexable_helper = $indexable_helper; + $this->image_content_extractor = $image_content_extractor; } /** @@ -137,7 +145,7 @@ public function build( $indexable, $content ) { $content = \str_replace( ']]>', ']]>', $content ); $links = $this->gather_links( $content ); - $images = $this->gather_images( $content ); + $images = $this->image_content_extractor->gather_images( $content ); if ( empty( $links ) && empty( $images ) ) { $indexable->link_count = 0; @@ -229,164 +237,6 @@ protected function gather_links( $content ) { return $links; } - /** - * Gathers all images from content with WP's WP_HTML_Tag_Processor() and returns them along with their IDs, if - * possible. - * - * @param string $content The content. - * - * @return int[] An associated array of image IDs, keyed by their URL. - */ - protected function gather_images_wp( $content ) { - $processor = new WP_HTML_Tag_Processor( $content ); - $images = []; - - $query = [ - 'tag_name' => 'img', - ]; - - /** - * Filter 'wpseo_image_attribute_containing_id' - Allows filtering what attribute will be used to extract image IDs from. - * - * Defaults to "class", which is where WP natively stores the image IDs, in a `wp-image-` format. - * - * @api string The attribute to be used to extract image IDs from. - */ - $attribute = \apply_filters( 'wpseo_image_attribute_containing_id', 'class' ); - - while ( $processor->next_tag( $query ) ) { - $src = \htmlentities( $processor->get_attribute( 'src' ), ( \ENT_QUOTES | \ENT_SUBSTITUTE | \ENT_HTML401 ), \get_bloginfo( 'charset' ) ); - $classes = $processor->get_attribute( $attribute ); - $id = $this->extract_id_of_classes( $classes ); - - $images[ $src ] = $id; - } - - return $images; - } - - /** - * Gathers all images from content with DOMDocument() and returns them along with their IDs, if possible. - * - * @param string $content The content. - * - * @return int[] An associated array of image IDs, keyed by their URL. - */ - protected function gather_images_domdocument( $content ) { - $images = []; - $charset = \get_bloginfo( 'charset' ); - - /** - * Filter 'wpseo_image_attribute_containing_id' - Allows filtering what attribute will be used to extract image IDs from. - * - * Defaults to "class", which is where WP natively stores the image IDs, in a `wp-image-` format. - * - * @api string The attribute to be used to extract image IDs from. - */ - $attribute = \apply_filters( 'wpseo_image_attribute_containing_id', 'class' ); - - \libxml_use_internal_errors( true ); - $post_dom = new DOMDocument(); - $post_dom->loadHTML( '' . $content ); - \libxml_clear_errors(); - - foreach ( $post_dom->getElementsByTagName( 'img' ) as $img ) { - $src = \htmlentities( $img->getAttribute( 'src' ), ( \ENT_QUOTES | \ENT_SUBSTITUTE | \ENT_HTML401 ), $charset ); - $classes = $img->getAttribute( $attribute ); - $id = $this->extract_id_of_classes( $classes ); - - $images[ $src ] = $id; - } - - return $images; - } - - /** - * Extracts image ID out of the image's classes. - * - * @param string $classes The classes assigned to the image. - * - * @return int The ID that's extracted from the classes. - */ - protected function extract_id_of_classes( $classes ) { - if ( ! $classes ) { - return 0; - } - - /** - * Filter 'wpseo_extract_id_pattern' - Allows filtering the regex patern to be used to extract image IDs from class/attribute names. - * - * Defaults to the pattern that extracts image IDs from core's `wp-image-` native format in image classes. - * - * @api string The regex pattern to be used to extract image IDs from class names. Empty string if the whole class/attribute should be returned. - */ - $pattern = \apply_filters( 'wpseo_extract_id_pattern', '/(?gather_images_wp( $content ); - } - - if ( ! $should_not_parse_content && \class_exists( DOMDocument::class ) ) { - return $this->gather_images_DOMDocument( $content ); - } - - if ( \strpos( $content, 'src' ) === false ) { - // Nothing to do. - return []; - } - - $images = []; - $regexp = ']*src=("??)([^" >]*?)\\1[^>]*>'; - // Used modifiers iU to match case insensitive and make greedy quantifiers lazy. - if ( \preg_match_all( "/$regexp/iU", $content, $matches, \PREG_SET_ORDER ) ) { - foreach ( $matches as $match ) { - $images[ $match[2] ] = 0; - } - } - - return $images; - } - /** * Creates link models from lists of URLs and image sources. * diff --git a/src/builders/indexable-post-builder.php b/src/builders/indexable-post-builder.php index 16eef3e1ffe..524e6a12255 100644 --- a/src/builders/indexable-post-builder.php +++ b/src/builders/indexable-post-builder.php @@ -378,6 +378,8 @@ protected function find_alternative_image( Indexable $indexable ) { } $content_image = $this->image->get_post_content_image( $indexable->object_id ); + \var_dump( $content_image ); + die; if ( $content_image ) { return [ 'image' => $content_image, diff --git a/src/images/Application/Image_Content_Extractor.php b/src/images/Application/Image_Content_Extractor.php new file mode 100644 index 00000000000..3c718508620 --- /dev/null +++ b/src/images/Application/Image_Content_Extractor.php @@ -0,0 +1,166 @@ +gather_images_wp( $content ); + } + + if ( ! $should_not_parse_content && \class_exists( DOMDocument::class ) ) { + return $this->gather_images_DOMDocument( $content ); + } + + if ( \strpos( $content, 'src' ) === false ) { + // Nothing to do. + return []; + } + + $images = []; + $regexp = ']*src=("??)([^" >]*?)\\1[^>]*>'; + // Used modifiers iU to match case insensitive and make greedy quantifiers lazy. + if ( \preg_match_all( "/$regexp/iU", $content, $matches, \PREG_SET_ORDER ) ) { + foreach ( $matches as $match ) { + $images[ $match[2] ] = 0; + } + } + + return $images; + } + + /** + * Gathers all images from content with WP's WP_HTML_Tag_Processor() and returns them along with their IDs, if + * possible. + * + * @param string $content The content. + * + * @return int[] An associated array of image IDs, keyed by their URL. + */ + protected function gather_images_wp( $content ) { + $processor = new WP_HTML_Tag_Processor( $content ); + $images = []; + + $query = [ + 'tag_name' => 'img', + ]; + + /** + * Filter 'wpseo_image_attribute_containing_id' - Allows filtering what attribute will be used to extract image IDs from. + * + * Defaults to "class", which is where WP natively stores the image IDs, in a `wp-image-` format. + * + * @api string The attribute to be used to extract image IDs from. + */ + $attribute = \apply_filters( 'wpseo_image_attribute_containing_id', 'class' ); + + while ( $processor->next_tag( $query ) ) { + $src = \htmlentities( $processor->get_attribute( 'src' ), ( \ENT_QUOTES | \ENT_SUBSTITUTE | \ENT_HTML401 ), \get_bloginfo( 'charset' ) ); + $classes = $processor->get_attribute( $attribute ); + $id = $this->extract_id_of_classes( $classes ); + + $images[ $src ] = $id; + } + + return $images; + } + + /** + * Gathers all images from content with DOMDocument() and returns them along with their IDs, if possible. + * + * @param string $content The content. + * + * @return int[] An associated array of image IDs, keyed by their URL. + */ + protected function gather_images_domdocument( $content ) { + $images = []; + $charset = \get_bloginfo( 'charset' ); + + /** + * Filter 'wpseo_image_attribute_containing_id' - Allows filtering what attribute will be used to extract image IDs from. + * + * Defaults to "class", which is where WP natively stores the image IDs, in a `wp-image-` format. + * + * @api string The attribute to be used to extract image IDs from. + */ + $attribute = \apply_filters( 'wpseo_image_attribute_containing_id', 'class' ); + + \libxml_use_internal_errors( true ); + $post_dom = new DOMDocument(); + $post_dom->loadHTML( '' . $content ); + \libxml_clear_errors(); + + foreach ( $post_dom->getElementsByTagName( 'img' ) as $img ) { + $src = \htmlentities( $img->getAttribute( 'src' ), ( \ENT_QUOTES | \ENT_SUBSTITUTE | \ENT_HTML401 ), $charset ); + $classes = $img->getAttribute( $attribute ); + $id = $this->extract_id_of_classes( $classes ); + + $images[ $src ] = $id; + } + + return $images; + } + + /** + * Extracts image ID out of the image's classes. + * + * @param string $classes The classes assigned to the image. + * + * @return int The ID that's extracted from the classes. + */ + protected function extract_id_of_classes( $classes ) { + if ( ! $classes ) { + return 0; + } + + /** + * Filter 'wpseo_extract_id_pattern' - Allows filtering the regex patern to be used to extract image IDs from class/attribute names. + * + * Defaults to the pattern that extracts image IDs from core's `wp-image-` native format in image classes. + * + * @api string The regex pattern to be used to extract image IDs from class names. Empty string if the whole class/attribute should be returned. + */ + $pattern = \apply_filters( 'wpseo_extract_id_pattern', '/(? Date: Mon, 12 Aug 2024 09:18:55 +0200 Subject: [PATCH 015/313] Use new class to get image ID --- src/builders/indexable-post-builder.php | 5 ++-- src/helpers/image-helper.php | 35 ++++++++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/builders/indexable-post-builder.php b/src/builders/indexable-post-builder.php index 524e6a12255..4f6a1b7d925 100644 --- a/src/builders/indexable-post-builder.php +++ b/src/builders/indexable-post-builder.php @@ -378,11 +378,10 @@ protected function find_alternative_image( Indexable $indexable ) { } $content_image = $this->image->get_post_content_image( $indexable->object_id ); - \var_dump( $content_image ); - die; + if ( $content_image ) { return [ - 'image' => $content_image, + 'image_id' => $content_image, 'source' => 'first-content-image', ]; } diff --git a/src/helpers/image-helper.php b/src/helpers/image-helper.php index 560c7eb9b62..578a4840a48 100644 --- a/src/helpers/image-helper.php +++ b/src/helpers/image-helper.php @@ -3,6 +3,7 @@ namespace Yoast\WP\SEO\Helpers; use WPSEO_Image_Utils; +use Yoast\WP\SEO\Images\Application\Image_Content_Extractor; use Yoast\WP\SEO\Models\SEO_Links; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Repositories\SEO_Links_Repository; @@ -39,6 +40,10 @@ class Image_Helper { * @var SEO_Links_Repository */ protected $seo_links_repository; + /** + * @var \Yoast\WP\SEO\Images\Application\Image_Content_Extractor + */ + protected $image_content_extractor; /** * The options helper. @@ -66,12 +71,14 @@ public function __construct( Indexable_Repository $indexable_repository, SEO_Links_Repository $seo_links_repository, Options_Helper $options, - Url_Helper $url_helper + Url_Helper $url_helper, + Image_Content_Extractor $image_content_extractor ) { - $this->indexable_repository = $indexable_repository; - $this->seo_links_repository = $seo_links_repository; - $this->options_helper = $options; - $this->url_helper = $url_helper; + $this->indexable_repository = $indexable_repository; + $this->seo_links_repository = $seo_links_repository; + $this->options_helper = $options; + $this->url_helper = $url_helper; + $this->image_content_extractor = $image_content_extractor; } /** @@ -366,7 +373,8 @@ public function get_attachment_id_from_settings( $setting ) { } /** - * Based on and image ID return array with the best variation of that image. If it's not saved to the DB, save it to an option. + * Based on and image ID return array with the best variation of that image. If it's not saved to the DB, save it + * to an option. * * @param string $setting The setting name. Should be company or person. * @@ -399,7 +407,20 @@ public function get_attachment_meta_from_settings( $setting ) { * @return string|null */ protected function get_first_usable_content_image_for_post( $post_id ) { - return WPSEO_Image_Utils::get_first_usable_content_image_for_post( $post_id ); + global $post; + $post_backup = $post; + $post = \get_post( $post_id ); + + \setup_postdata( $post ); + $content = \apply_filters( 'the_content', $post->post_content ); + \wp_reset_postdata(); + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- To setup the post we need to do this explicitly. + $post = $post_backup; + + $content = \str_replace( ']]>', ']]>', $content ); + $images = $this->image_content_extractor->gather_images( $content ); + + return array_shift( $images ); } /** From eb5ca38b7f705a6a09d4bb43b4bbeed1c36746b8 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Mon, 12 Aug 2024 13:05:33 +0200 Subject: [PATCH 016/313] Make sure to fallback on old regex in case no suitable image tags are found. --- inc/class-wpseo-content-images.php | 74 ------------------- src/builders/indexable-post-builder.php | 12 ++- src/helpers/image-helper.php | 35 ++++++++- .../Application/Image_Content_Extractor.php | 3 +- 4 files changed, 43 insertions(+), 81 deletions(-) diff --git a/inc/class-wpseo-content-images.php b/inc/class-wpseo-content-images.php index aa06980e90d..bae91bc6360 100644 --- a/inc/class-wpseo-content-images.php +++ b/inc/class-wpseo-content-images.php @@ -30,92 +30,18 @@ public function get_images( $post_id, $post = null ) { * @return array An array of image URLs. */ public function get_images_from_content( $content ) { - - if ( ! is_string( $content ) ) { return []; } - $content_images2 = $this->gather_images_wp( $content); $content_images = $this->get_img_tags_from_content( $content ); $images = array_map( [ $this, 'get_img_tag_source' ], $content_images ); $images = array_filter( $images ); $images = array_unique( $images ); $images = array_values( $images ); // Reset the array keys. - var_dump( $content_images2 ); - var_dump( $images ); - die; - return $images; - } - /** - * Gathers all images from content with WP's WP_HTML_Tag_Processor() and returns them along with their IDs, if - * possible. - * - * @param string $content The content. - * - * @return int[] An associated array of image IDs, keyed by their URL. - */ - protected function gather_images_wp( $content ) { - $processor = new WP_HTML_Tag_Processor( $content ); - $images = []; - - $query = [ - 'tag_name' => 'img', - ]; - - /** - * Filter 'wpseo_image_attribute_containing_id' - Allows filtering what attribute will be used to extract image IDs from. - * - * Defaults to "class", which is where WP natively stores the image IDs, in a `wp-image-` format. - * - * @api string The attribute to be used to extract image IDs from. - */ - $attribute = \apply_filters( 'wpseo_image_attribute_containing_id', 'class' ); - - while ( $processor->next_tag( $query ) ) { - $src = \htmlentities( $processor->get_attribute( 'src' ), ( \ENT_QUOTES | \ENT_SUBSTITUTE | \ENT_HTML401 ), \get_bloginfo( 'charset' ) ); - $classes = $processor->get_attribute( $attribute ); - $id = $this->extract_id_of_classes( $classes ); - - $images[ $src ] = $id; - } - return $images; } - /** - * Extracts image ID out of the image's classes. - * - * @param string $classes The classes assigned to the image. - * - * @return int The ID that's extracted from the classes. - */ - protected function extract_id_of_classes( $classes ) { - if ( ! $classes ) { - return 0; - } - - /** - * Filter 'wpseo_extract_id_pattern' - Allows filtering the regex patern to be used to extract image IDs from class/attribute names. - * - * Defaults to the pattern that extracts image IDs from core's `wp-image-` native format in image classes. - * - * @api string The regex pattern to be used to extract image IDs from class names. Empty string if the whole class/attribute should be returned. - */ - $pattern = \apply_filters( 'wpseo_extract_id_pattern', '/(?image->get_post_content_image( $indexable->object_id ); + $content_image = $this->image->get_post_content_image_id( $indexable->object_id ); + + if ( $content_image !== "" ) { + return [ + 'image_id' => $content_image, + 'source' => 'first-content-image', + ]; + } + $content_image = $this->image->get_post_content_image( $indexable->object_id ); if ( $content_image ) { return [ - 'image_id' => $content_image, + 'image' => $content_image, 'source' => 'first-content-image', ]; } diff --git a/src/helpers/image-helper.php b/src/helpers/image-helper.php index 578a4840a48..1bf77b814b1 100644 --- a/src/helpers/image-helper.php +++ b/src/helpers/image-helper.php @@ -157,7 +157,24 @@ public function get_featured_image_id( $post_id ) { } /** - * Gets the image url from the content. + * Gets the image ic from the content. + * + * @param int $post_id The post id to extract the images from. + * + * @return string The image url or an empty string when not found. + */ + public function get_post_content_image_id( $post_id ) { + $image_url = $this->get_first_usable_content_image_id_for_post( $post_id ); + + if ( $image_url === null ) { + return ''; + } + + return $image_url; + } + + /** + * Gets the image ic from the content. * * @param int $post_id The post id to extract the images from. * @@ -398,7 +415,7 @@ public function get_attachment_meta_from_settings( $setting ) { } /** - * Retrieves the first usable content image for a post. + * Retrieves the first usable content image_id for a post. * * @codeCoverageIgnore - We have to write test when this method contains own code. * @@ -406,7 +423,7 @@ public function get_attachment_meta_from_settings( $setting ) { * * @return string|null */ - protected function get_first_usable_content_image_for_post( $post_id ) { + protected function get_first_usable_content_image_id_for_post( $post_id ) { global $post; $post_backup = $post; $post = \get_post( $post_id ); @@ -422,6 +439,18 @@ protected function get_first_usable_content_image_for_post( $post_id ) { return array_shift( $images ); } + /** + * Retrieves the first usable content image for a post. + * + * @codeCoverageIgnore - We have to write test when this method contains own code. + * + * @param int $post_id The post id to extract the images from. + * + * @return string|null + */ + protected function get_first_usable_content_image_for_post( $post_id ) { + return WPSEO_Image_Utils::get_first_usable_content_image_for_post( $post_id ); + } /** * Gets the term's first usable content image. Null if none is available. diff --git a/src/images/Application/Image_Content_Extractor.php b/src/images/Application/Image_Content_Extractor.php index 3c718508620..304412d4894 100644 --- a/src/images/Application/Image_Content_Extractor.php +++ b/src/images/Application/Image_Content_Extractor.php @@ -23,7 +23,6 @@ public function gather_images( $content ) { * @since 21.1 */ $should_not_parse_content = \apply_filters( 'wpseo_force_creating_and_using_attachment_indexables', false ); - /** * Filter 'wpseo_force_skip_image_content_parsing' - Filters if we should force skip scanning the content to parse images. * This filter can be used if the regex gives a faster result than scanning the code. @@ -41,6 +40,7 @@ public function gather_images( $content ) { return $this->gather_images_DOMDocument( $content ); } + if ( \strpos( $content, 'src' ) === false ) { // Nothing to do. return []; @@ -82,7 +82,6 @@ protected function gather_images_wp( $content ) { * @api string The attribute to be used to extract image IDs from. */ $attribute = \apply_filters( 'wpseo_image_attribute_containing_id', 'class' ); - while ( $processor->next_tag( $query ) ) { $src = \htmlentities( $processor->get_attribute( 'src' ), ( \ENT_QUOTES | \ENT_SUBSTITUTE | \ENT_HTML401 ), \get_bloginfo( 'charset' ) ); $classes = $processor->get_attribute( $attribute ); From b46bdddb57842e556b1de0b8460b445348102b3f Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Mon, 12 Aug 2024 14:01:29 +0200 Subject: [PATCH 017/313] Start fixing tests --- src/builders/indexable-link-builder.php | 14 +++++++------- src/builders/indexable-post-builder.php | 2 +- src/helpers/image-helper.php | 6 ++++-- src/images/Application/Image_Content_Extractor.php | 2 +- .../Abstract_Indexable_Link_Builder_TestCase.php | 12 +++++++++++- tests/Unit/Helpers/Image_Helper_Test.php | 10 +++++++++- 6 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/builders/indexable-link-builder.php b/src/builders/indexable-link-builder.php index 90c4e00502e..0a6bc0eab16 100644 --- a/src/builders/indexable-link-builder.php +++ b/src/builders/indexable-link-builder.php @@ -2,7 +2,6 @@ namespace Yoast\WP\SEO\Builders; - use WPSEO_Image_Utils; use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Indexable_Helper; @@ -71,6 +70,7 @@ class Indexable_Link_Builder { /** * Class that finds all images in a content string and extracts them. + * * @var Image_Content_Extractor */ private $image_content_extractor; @@ -92,12 +92,12 @@ public function __construct( Indexable_Helper $indexable_helper, Image_Content_Extractor $image_content_extractor ) { - $this->seo_links_repository = $seo_links_repository; - $this->url_helper = $url_helper; - $this->post_helper = $post_helper; - $this->options_helper = $options_helper; - $this->indexable_helper = $indexable_helper; - $this->image_content_extractor = $image_content_extractor; + $this->seo_links_repository = $seo_links_repository; + $this->url_helper = $url_helper; + $this->post_helper = $post_helper; + $this->options_helper = $options_helper; + $this->indexable_helper = $indexable_helper; + $this->image_content_extractor = $image_content_extractor; } /** diff --git a/src/builders/indexable-post-builder.php b/src/builders/indexable-post-builder.php index 42937c78438..4837766b6b4 100644 --- a/src/builders/indexable-post-builder.php +++ b/src/builders/indexable-post-builder.php @@ -379,7 +379,7 @@ protected function find_alternative_image( Indexable $indexable ) { $content_image = $this->image->get_post_content_image_id( $indexable->object_id ); - if ( $content_image !== "" ) { + if ( $content_image !== '' ) { return [ 'image_id' => $content_image, 'source' => 'first-content-image', diff --git a/src/helpers/image-helper.php b/src/helpers/image-helper.php index 1bf77b814b1..effb8e1c9b5 100644 --- a/src/helpers/image-helper.php +++ b/src/helpers/image-helper.php @@ -40,8 +40,9 @@ class Image_Helper { * @var SEO_Links_Repository */ protected $seo_links_repository; + /** - * @var \Yoast\WP\SEO\Images\Application\Image_Content_Extractor + * @var Image_Content_Extractor */ protected $image_content_extractor; @@ -437,8 +438,9 @@ protected function get_first_usable_content_image_id_for_post( $post_id ) { $content = \str_replace( ']]>', ']]>', $content ); $images = $this->image_content_extractor->gather_images( $content ); - return array_shift( $images ); + return \array_shift( $images ); } + /** * Retrieves the first usable content image for a post. * diff --git a/src/images/Application/Image_Content_Extractor.php b/src/images/Application/Image_Content_Extractor.php index 304412d4894..23bc193fd99 100644 --- a/src/images/Application/Image_Content_Extractor.php +++ b/src/images/Application/Image_Content_Extractor.php @@ -6,6 +6,7 @@ use WP_HTML_Tag_Processor; class Image_Content_Extractor { + /** * Gathers all images from content. * @@ -40,7 +41,6 @@ public function gather_images( $content ) { return $this->gather_images_DOMDocument( $content ); } - if ( \strpos( $content, 'src' ) === false ) { // Nothing to do. return []; diff --git a/tests/Unit/Builders/Indexable_Link_Builder/Abstract_Indexable_Link_Builder_TestCase.php b/tests/Unit/Builders/Indexable_Link_Builder/Abstract_Indexable_Link_Builder_TestCase.php index a1258c61dbf..d52d479c098 100644 --- a/tests/Unit/Builders/Indexable_Link_Builder/Abstract_Indexable_Link_Builder_TestCase.php +++ b/tests/Unit/Builders/Indexable_Link_Builder/Abstract_Indexable_Link_Builder_TestCase.php @@ -11,6 +11,7 @@ use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Post_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; +use Yoast\WP\SEO\Images\Application\Image_Content_Extractor; use Yoast\WP\SEO\Models\SEO_Links; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Repositories\SEO_Links_Repository; @@ -84,6 +85,13 @@ abstract class Abstract_Indexable_Link_Builder_TestCase extends TestCase { */ protected $image_url; + /** + * The Image content extractor instance. + * + * @var Mockery\MockInterface|Image_Content_Extractor + */ + protected $image_content_extractor; + /** * Sets up the tests. * @@ -100,12 +108,14 @@ protected function set_up() { $this->options_helper = Mockery::mock( Options_Helper::class ); $this->indexable_helper = Mockery::mock( Indexable_Helper::class ); + $this->image_content_extractor = Mockery::mock( Image_Content_Extractor::class ); $this->instance = new Indexable_Link_Builder( $this->seo_links_repository, $this->url_helper, $this->post_helper, $this->options_helper, - $this->indexable_helper + $this->indexable_helper, + $this->image_content_extractor ); $this->instance->set_dependencies( $this->indexable_repository, $this->image_helper ); diff --git a/tests/Unit/Helpers/Image_Helper_Test.php b/tests/Unit/Helpers/Image_Helper_Test.php index 1a31b936062..b2fc22d7ed1 100644 --- a/tests/Unit/Helpers/Image_Helper_Test.php +++ b/tests/Unit/Helpers/Image_Helper_Test.php @@ -7,6 +7,7 @@ use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; +use Yoast\WP\SEO\Images\Application\Image_Content_Extractor; use Yoast\WP\SEO\Models\SEO_Links; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Repositories\SEO_Links_Repository; @@ -64,6 +65,12 @@ final class Image_Helper_Test extends TestCase { * @var Mockery\MockInterface|Url_Helper */ protected $url_helper; + /** + * The Image content extractor instance. + * + * @var Mockery\MockInterface|Image_Content_Extractor + */ + protected $image_content_extractor; /** * Setup. @@ -81,8 +88,9 @@ protected function set_up() { $this->indexable_seo_links_repository = Mockery::mock( SEO_Links_Repository::class ); $this->options_helper = Mockery::mock( Options_Helper::class ); $this->url_helper = Mockery::mock( Url_Helper::class ); + $this->image_content_extractor = Mockery::mock( Image_Content_Extractor::class ); - $this->actual_instance = new Image_Helper( $this->indexable_repository, $this->indexable_seo_links_repository, $this->options_helper, $this->url_helper ); + $this->actual_instance = new Image_Helper( $this->indexable_repository, $this->indexable_seo_links_repository, $this->options_helper, $this->url_helper,$this->image_content_extractor ); } /** From 19d436b56c3ff7e0459b9592561d715316a4a51d Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 14 Aug 2024 08:32:21 +0200 Subject: [PATCH 018/313] Start fixing tests --- src/helpers/image-helper.php | 12 ++-- ...ractor.php => image-content-extractor.php} | 3 + ...stract_Indexable_Link_Builder_TestCase.php | 18 ++--- .../Indexable_Link_Builder/Build_Test.php | 61 ++++++++++------- .../Create_Internal_Link_Test.php | 11 ++-- .../Get_Permalink_Test.php | 3 +- ...Update_Incoming_Links_For_Related_Test.php | 3 +- .../Builders/Indexable_Post_Builder_Test.php | 66 ++++++++++++++++++- tests/Unit/Helpers/Image_Helper_Test.php | 5 +- .../Background_Indexing_Integration_Test.php | 2 +- 10 files changed, 136 insertions(+), 48 deletions(-) rename src/images/Application/{Image_Content_Extractor.php => image-content-extractor.php} (99%) diff --git a/src/helpers/image-helper.php b/src/helpers/image-helper.php index effb8e1c9b5..8943426a0ef 100644 --- a/src/helpers/image-helper.php +++ b/src/helpers/image-helper.php @@ -42,6 +42,8 @@ class Image_Helper { protected $seo_links_repository; /** + * The image content extractor. + * * @var Image_Content_Extractor */ protected $image_content_extractor; @@ -63,10 +65,11 @@ class Image_Helper { /** * Image_Helper constructor. * - * @param Indexable_Repository $indexable_repository The indexable repository. - * @param SEO_Links_Repository $seo_links_repository The SEO Links repository. - * @param Options_Helper $options The options helper. - * @param Url_Helper $url_helper The URL helper. + * @param Indexable_Repository $indexable_repository The indexable repository. + * @param SEO_Links_Repository $seo_links_repository The SEO Links repository. + * @param Options_Helper $options The options helper. + * @param Url_Helper $url_helper The URL helper. + * @param Image_Content_Extractor $image_content_extractor The image content extractor. */ public function __construct( Indexable_Repository $indexable_repository, @@ -425,6 +428,7 @@ public function get_attachment_meta_from_settings( $setting ) { * @return string|null */ protected function get_first_usable_content_image_id_for_post( $post_id ) { + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- To setup the post we need to do this explicitly. global $post; $post_backup = $post; $post = \get_post( $post_id ); diff --git a/src/images/Application/Image_Content_Extractor.php b/src/images/Application/image-content-extractor.php similarity index 99% rename from src/images/Application/Image_Content_Extractor.php rename to src/images/Application/image-content-extractor.php index 23bc193fd99..d7bb4324a3a 100644 --- a/src/images/Application/Image_Content_Extractor.php +++ b/src/images/Application/image-content-extractor.php @@ -5,6 +5,9 @@ use DOMDocument; use WP_HTML_Tag_Processor; +/** + * The image content extractor. + */ class Image_Content_Extractor { /** diff --git a/tests/Unit/Builders/Indexable_Link_Builder/Abstract_Indexable_Link_Builder_TestCase.php b/tests/Unit/Builders/Indexable_Link_Builder/Abstract_Indexable_Link_Builder_TestCase.php index d52d479c098..09ea9828d44 100644 --- a/tests/Unit/Builders/Indexable_Link_Builder/Abstract_Indexable_Link_Builder_TestCase.php +++ b/tests/Unit/Builders/Indexable_Link_Builder/Abstract_Indexable_Link_Builder_TestCase.php @@ -100,15 +100,15 @@ abstract class Abstract_Indexable_Link_Builder_TestCase extends TestCase { protected function set_up() { parent::set_up(); - $this->seo_links_repository = Mockery::mock( SEO_Links_Repository::class ); - $this->url_helper = Mockery::mock( Url_Helper::class ); - $this->indexable_repository = Mockery::mock( Indexable_Repository::class ); - $this->image_helper = Mockery::mock( Image_Helper::class ); - $this->post_helper = Mockery::mock( Post_Helper::class ); - $this->options_helper = Mockery::mock( Options_Helper::class ); - $this->indexable_helper = Mockery::mock( Indexable_Helper::class ); - - $this->image_content_extractor = Mockery::mock( Image_Content_Extractor::class ); + $this->seo_links_repository = Mockery::mock( SEO_Links_Repository::class ); + $this->url_helper = Mockery::mock( Url_Helper::class ); + $this->indexable_repository = Mockery::mock( Indexable_Repository::class ); + $this->image_helper = Mockery::mock( Image_Helper::class ); + $this->post_helper = Mockery::mock( Post_Helper::class ); + $this->options_helper = Mockery::mock( Options_Helper::class ); + $this->indexable_helper = Mockery::mock( Indexable_Helper::class ); + $this->image_content_extractor = Mockery::mock( Image_Content_Extractor::class ); + $this->instance = new Indexable_Link_Builder( $this->seo_links_repository, $this->url_helper, diff --git a/tests/Unit/Builders/Indexable_Link_Builder/Build_Test.php b/tests/Unit/Builders/Indexable_Link_Builder/Build_Test.php index 23de91e49f6..75ffe8eec53 100644 --- a/tests/Unit/Builders/Indexable_Link_Builder/Build_Test.php +++ b/tests/Unit/Builders/Indexable_Link_Builder/Build_Test.php @@ -25,7 +25,7 @@ final class Build_Test extends Abstract_Indexable_Link_Builder_TestCase { /** * Data provider to test the build. * - * @return array The test data. + * @return array> The test data. */ public static function build_provider() { return [ @@ -39,6 +39,7 @@ public static function build_provider() { false, false, false, + [], ], [ ' @@ -50,6 +51,10 @@ public static function build_provider() { false, false, true, + [ + 'https://link.com/newly-added-in-post' => 1, + 'https://link.com/already-existed-in-post' => 2, + ], ], [ ' @@ -61,6 +66,10 @@ public static function build_provider() { true, false, false, + [ + 'https://link.com/newly-added-in-post' => 1, + 'https://link.com/already-existed-in-post' => 2, + ], ], [ ' @@ -72,6 +81,10 @@ public static function build_provider() { false, true, true, + [ + 'https://link.com/newly-added-in-post' => 1, + 'https://link.com/already-existed-in-post' => 2, + ], ], [ ' @@ -83,6 +96,10 @@ public static function build_provider() { false, true, true, + [ + 'https://link.com/newly-added-in-post' => 1, + 'https://link.com/already-existed-in-post' => 2, + ], ], ]; } @@ -98,16 +115,17 @@ public static function build_provider() { * * @dataProvider build_provider * - * @param string $content The content. - * @param string $link_type The link type. - * @param bool $is_image Whether the link is an image. - * @param bool $ignore_content_scan Whether content scanning should be ignored. - * @param bool $should_content_regex Whether the image id should be extracted with a regex. - * @param bool $should_doc_scan Whether the doc document should be used. + * @param string $content The content. + * @param string $link_type The link type. + * @param bool $is_image Whether the link is an image. + * @param bool $ignore_content_scan Whether content scanning should be ignored. + * @param bool $should_content_regex Whether the image id should be extracted with a regex. + * @param bool $should_doc_scan Whether the doc document should be used. + * @param array $images The images that are in the content. * * @return void */ - public function test_build( $content, $link_type, $is_image, $ignore_content_scan, $should_content_regex, $should_doc_scan ) { + public function test_build( $content, $link_type, $is_image, $ignore_content_scan, $should_content_regex, $should_doc_scan, $images ) { $indexable = Mockery::mock( Indexable_Mock::class ); $indexable->id = 1; $indexable->object_id = 2; @@ -116,6 +134,13 @@ public function test_build( $content, $link_type, $is_image, $ignore_content_sca $this->indexable_helper->expects( 'should_index_indexable' )->once()->andReturn( true ); $this->post_helper->expects( 'get_post' )->once()->with( 2 )->andReturn( 'post' ); + if ( $is_image ) { + $this->image_content_extractor->expects( 'gather_images' )->once()->andReturn( $images ); + } + else { + $this->image_content_extractor->expects( 'gather_images' )->once()->andReturn( [] ); + + } Functions\expect( 'setup_postdata' )->once()->with( 'post' ); Functions\expect( 'apply_filters' )->once()->with( 'the_content', $content )->andReturn( $content ); Functions\expect( 'wp_reset_postdata' )->once(); @@ -144,21 +169,6 @@ public function test_build( $content, $link_type, $is_image, $ignore_content_sca Functions\expect( 'home_url' )->once()->andReturn( 'https://site.com' ); Functions\expect( 'wp_parse_url' )->once()->with( 'https://site.com' )->andReturn( $parsed_home_url ); Functions\expect( 'wp_parse_url' )->once()->with( 'https://site.com/page' )->andReturn( $parsed_page_url ); - if ( $should_doc_scan ) { - Functions\expect( 'apply_filters' ) - ->once() - ->with( 'wpseo_image_attribute_containing_id', 'class' ) - ->andReturn( 'class' ); - } - if ( $should_content_regex ) { - Functions\expect( 'apply_filters' ) - ->twice() - ->with( 'wpseo_extract_id_pattern', '/(?andReturn( '/(?once()->with( 'wpseo_force_skip_image_content_parsing', false )->andReturn( true ); - } // Inside create_links->create_internal_link method. Functions\expect( 'wp_parse_url' )->once()->with( 'https://link.com/newly-added-in-post' )->andReturn( $parsed_new_link_url ); @@ -261,6 +271,8 @@ public function test_build_target_indexable_does_not_exist() { $this->indexable_helper->expects( 'should_index_indexable' )->once()->andReturn( true ); $this->post_helper->expects( 'get_post' )->once()->with( 2 )->andReturn( 'post' ); + $this->image_content_extractor->expects( 'gather_images' )->once()->andReturn( [] ); + Functions\expect( 'setup_postdata' )->once()->with( 'post' ); Filters\expectApplied( 'the_content' )->with( $content )->andReturnFirstArg(); Functions\expect( 'wp_reset_postdata' )->once(); @@ -375,6 +387,7 @@ public function test_build_no_links() { $indexable->permalink = 'https://site.com/page'; $this->indexable_helper->expects( 'should_index_indexable' )->once()->andReturn( true ); + $this->image_content_extractor->expects( 'gather_images' )->once()->andReturn( [] ); $this->seo_links_repository ->expects( 'find_all_by_indexable_id' ) @@ -406,6 +419,8 @@ public function test_build_ignore_content_scan( $input_content, $output_result ) $indexable->permalink = 'https://site.com/page'; $this->indexable_helper->expects( 'should_index_indexable' )->once()->andReturn( true ); + $this->image_content_extractor->expects( 'gather_images' )->once()->andReturn( [] ); + Functions\expect( 'apply_filters' )->andReturn( true ); $this->seo_links_repository diff --git a/tests/Unit/Builders/Indexable_Link_Builder/Create_Internal_Link_Test.php b/tests/Unit/Builders/Indexable_Link_Builder/Create_Internal_Link_Test.php index 55dc508e7ba..50b3c21d9a2 100644 --- a/tests/Unit/Builders/Indexable_Link_Builder/Create_Internal_Link_Test.php +++ b/tests/Unit/Builders/Indexable_Link_Builder/Create_Internal_Link_Test.php @@ -44,6 +44,7 @@ public function test_build_create_internal_link() { $model->type = SEO_Links::TYPE_INTERNAL_IMAGE; $this->indexable_helper->expects( 'should_index_indexable' )->once()->andReturn( true ); + $this->image_content_extractor->expects( 'gather_images' )->once()->andReturn( [ 'http://basic.wordpress.test/wp-content/uploads/2022/11/WordPress8.jpg?quality=90&grain=0.5' => 2 ] ); Functions\stubs( [ @@ -122,6 +123,7 @@ public function test_build_create_internal_link_disable_attachment_true_file_doe $model->target_post_id = 2; $this->indexable_helper->expects( 'should_index_indexable' )->once()->andReturn( true ); + $this->image_content_extractor->expects( 'gather_images' )->once()->andReturn( [ 'http://basic.wordpress.test/wp-content/uploads/2022/11/WordPress8.jpg?quality=90&grain=0.5' => 2 ] ); // Executed in build->create_links->create_internal_link. Functions\stubs( @@ -155,12 +157,6 @@ public function test_build_create_internal_link_disable_attachment_true_file_doe // Executed in build->create_links->create_internal_link. $this->expect_seo_links_repository_query_create( $indexable, $model ); - // Executed in build->create_links->create_internal_link->WPSEO_Image_Utils::get_attachment_by_url. - Functions\expect( 'wp_get_upload_dir' ) - ->with( 'http://basic.wordpress.test/wp-content/uploads' ) - ->once() - ->andReturn( [ 'baseurl' => 'http://basic.wordpress.test/wp-content/uploads' ] ); - $this->expect_build_permalink( 'http://basic.wordpress.test' ); $this->options_helper @@ -203,6 +199,7 @@ public function test_build_create_internal_link_disable_attachment_true_file_exi $model->width = null; $this->indexable_helper->expects( 'should_index_indexable' )->once()->andReturn( true ); + $this->image_content_extractor->expects( 'gather_images' )->once()->andReturn( [ 'http://basic.wordpress.test/wp-content/uploads/2022/11/WordPress8.jpg?quality=90&grain=0.5' => 2 ] ); // Executed in build->create_links->create_internal_link. Functions\stubs( @@ -268,6 +265,7 @@ public function test_build_create_internal_link_disable_attachment_true_file_not $model->target_post_id = 3; $this->indexable_helper->expects( 'should_index_indexable' )->once()->andReturn( true ); + $this->image_content_extractor->expects( 'gather_images' )->once()->andReturn( [ 'http://basic.wordpress.test/wp-content/uploads/2022/11/WordPress8.jpg?quality=90&grain=0.5' => 2 ] ); Functions\stubs( [ @@ -348,6 +346,7 @@ public function test_build_create_internal_link_disable_attachment_true_get_atta $model->target_post_id = 2; $this->indexable_helper->expects( 'should_index_indexable' )->once()->andReturn( true ); + $this->image_content_extractor->expects( 'gather_images' )->once()->andReturn( [ 'http://basic.wordpress.test/wp-content/uploads/2022/11/WordPress8.jpg?quality=90&grain=0.5' => 2 ] ); Functions\stubs( [ diff --git a/tests/Unit/Builders/Indexable_Link_Builder/Get_Permalink_Test.php b/tests/Unit/Builders/Indexable_Link_Builder/Get_Permalink_Test.php index 7495510984d..0f2072c6566 100644 --- a/tests/Unit/Builders/Indexable_Link_Builder/Get_Permalink_Test.php +++ b/tests/Unit/Builders/Indexable_Link_Builder/Get_Permalink_Test.php @@ -26,7 +26,8 @@ protected function set_up() { $this->url_helper, $this->post_helper, $this->options_helper, - $this->indexable_helper + $this->indexable_helper, + $this->image_content_extractor ); $this->instance->set_dependencies( $this->indexable_repository, $this->image_helper ); diff --git a/tests/Unit/Builders/Indexable_Link_Builder/Update_Incoming_Links_For_Related_Test.php b/tests/Unit/Builders/Indexable_Link_Builder/Update_Incoming_Links_For_Related_Test.php index 92a04c7b4c3..ec0b1b5244f 100644 --- a/tests/Unit/Builders/Indexable_Link_Builder/Update_Incoming_Links_For_Related_Test.php +++ b/tests/Unit/Builders/Indexable_Link_Builder/Update_Incoming_Links_For_Related_Test.php @@ -28,7 +28,8 @@ protected function set_up() { $this->url_helper, $this->post_helper, $this->options_helper, - $this->indexable_helper + $this->indexable_helper, + $this->image_content_extractor ); $this->instance->set_dependencies( $this->indexable_repository, $this->image_helper ); diff --git a/tests/Unit/Builders/Indexable_Post_Builder_Test.php b/tests/Unit/Builders/Indexable_Post_Builder_Test.php index 45e64177db8..9231b223ca4 100644 --- a/tests/Unit/Builders/Indexable_Post_Builder_Test.php +++ b/tests/Unit/Builders/Indexable_Post_Builder_Test.php @@ -620,6 +620,10 @@ public function test_find_alternative_image_from_post_content() { ->with( 123 ) ->andReturn( $image_meta ); + $this->image->allows( 'get_post_content_image_id' ) + ->with( 123 ) + ->andReturn( '' ); + $actual = $this->instance->find_alternative_image( $this->indexable ); $expected = [ @@ -630,6 +634,64 @@ public function test_find_alternative_image_from_post_content() { $this->assertEquals( $expected, $actual ); } + /** + * Tests find_alternative_image_id when a image is set on the post, + * but not a featured image. + * + * @covers ::find_alternative_image + * + * @return void + */ + public function test_find_alternative_image_id_from_post_content() { + $this->indexable = Mockery::mock( Indexable::class ); + $this->indexable->orm = Mockery::mock( ORM::class ); + + $this->indexable->orm->allows( 'get' ) + ->with( 'object_sub_type' ) + ->andReturn( 'post' ); + + $this->indexable->orm->allows( 'get' ) + ->with( 'object_id' ) + ->andReturn( 123 ); + + $this->image->allows( 'get_featured_image_id' ) + ->with( 123 ) + ->andReturn( false ); + + $this->image->allows( 'get_gallery_image' ) + ->with( 123 ) + ->andReturn( false ); + + $image_meta = [ + 'width' => 640, + 'height' => 480, + 'url' => 'http://basic.wordpress.test/wp-content/uploads/2020/07/WordPress5.jpg', + 'path' => '/var/www/html/wp-content/uploads/2020/07/WordPress5.jpg', + 'size' => 'full', + 'id' => 13, + 'alt' => '', + 'pixels' => 307200, + 'type' => 'image/jpeg', + ]; + + $this->image->allows( 'get_post_content_image' ) + ->with( 123 ) + ->andReturn( $image_meta ); + + $this->image->allows( 'get_post_content_image_id' ) + ->with( 123 ) + ->andReturn( 13 ); + + $actual = $this->instance->find_alternative_image( $this->indexable ); + + $expected = [ + 'image_id' => $image_meta['id'], + 'source' => 'first-content-image', + ]; + + $this->assertEquals( $expected, $actual ); + } + /** * Tests find_alternative_image when a gallery image is set on the post, * but not a featured image. @@ -661,7 +723,9 @@ public function test_find_alternative_image_no_image() { $this->image->allows( 'get_post_content_image' ) ->with( 123 ) ->andReturn( false ); - + $this->image->allows( 'get_post_content_image_id' ) + ->with( 123 ) + ->andReturn( '' ); $this->assertFalse( $this->instance->find_alternative_image( $this->indexable ) ); } diff --git a/tests/Unit/Helpers/Image_Helper_Test.php b/tests/Unit/Helpers/Image_Helper_Test.php index b2fc22d7ed1..6f3940c57d5 100644 --- a/tests/Unit/Helpers/Image_Helper_Test.php +++ b/tests/Unit/Helpers/Image_Helper_Test.php @@ -65,6 +65,7 @@ final class Image_Helper_Test extends TestCase { * @var Mockery\MockInterface|Url_Helper */ protected $url_helper; + /** * The Image content extractor instance. * @@ -88,9 +89,9 @@ protected function set_up() { $this->indexable_seo_links_repository = Mockery::mock( SEO_Links_Repository::class ); $this->options_helper = Mockery::mock( Options_Helper::class ); $this->url_helper = Mockery::mock( Url_Helper::class ); - $this->image_content_extractor = Mockery::mock( Image_Content_Extractor::class ); + $this->image_content_extractor = Mockery::mock( Image_Content_Extractor::class ); - $this->actual_instance = new Image_Helper( $this->indexable_repository, $this->indexable_seo_links_repository, $this->options_helper, $this->url_helper,$this->image_content_extractor ); + $this->actual_instance = new Image_Helper( $this->indexable_repository, $this->indexable_seo_links_repository, $this->options_helper, $this->url_helper, $this->image_content_extractor ); } /** diff --git a/tests/Unit/Integrations/Admin/Background_Indexing_Integration_Test.php b/tests/Unit/Integrations/Admin/Background_Indexing_Integration_Test.php index 55404dd44c9..5ef22016158 100644 --- a/tests/Unit/Integrations/Admin/Background_Indexing_Integration_Test.php +++ b/tests/Unit/Integrations/Admin/Background_Indexing_Integration_Test.php @@ -744,7 +744,7 @@ public function test_schedule_cron_indexing( $admin_dashboard_conditional_result /** * Provides data to test_schedule_cron_indexing. * - * @return array The test data. + * @return array> The test data. */ public static function data_schedule_cron_indexing() { return [ From 00ebc4c75376513f7b9f0b6474003e8a0620183e Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 14 Aug 2024 13:49:20 +0200 Subject: [PATCH 019/313] Add tests. --- .../Application/image-content-extractor.php | 6 +- .../Indexable_Link_Builder/Build_Test.php | 32 ++---- .../Image_Content_Extractor_Test.php | 107 ++++++++++++++++++ 3 files changed, 121 insertions(+), 24 deletions(-) create mode 100644 tests/Unit/Images/Application/Image_Content_Extractor_Test.php diff --git a/src/images/Application/image-content-extractor.php b/src/images/Application/image-content-extractor.php index d7bb4324a3a..156df9d71a5 100644 --- a/src/images/Application/image-content-extractor.php +++ b/src/images/Application/image-content-extractor.php @@ -36,11 +36,13 @@ public function gather_images( $content ) { * @since 21.1 */ $should_not_parse_content = \apply_filters( 'wpseo_force_skip_image_content_parsing', $should_not_parse_content ); + if ( ! $should_not_parse_content && \class_exists( WP_HTML_Tag_Processor::class ) ) { return $this->gather_images_wp( $content ); } if ( ! $should_not_parse_content && \class_exists( DOMDocument::class ) ) { + return $this->gather_images_DOMDocument( $content ); } @@ -124,7 +126,8 @@ protected function gather_images_domdocument( $content ) { foreach ( $post_dom->getElementsByTagName( 'img' ) as $img ) { $src = \htmlentities( $img->getAttribute( 'src' ), ( \ENT_QUOTES | \ENT_SUBSTITUTE | \ENT_HTML401 ), $charset ); $classes = $img->getAttribute( $attribute ); - $id = $this->extract_id_of_classes( $classes ); + + $id = $this->extract_id_of_classes( $classes ); $images[ $src ] = $id; } @@ -160,6 +163,7 @@ protected function extract_id_of_classes( $classes ) { $matches = []; if ( \preg_match( $pattern, $classes, $matches ) ) { + return (int) $matches[1]; } diff --git a/tests/Unit/Builders/Indexable_Link_Builder/Build_Test.php b/tests/Unit/Builders/Indexable_Link_Builder/Build_Test.php index 75ffe8eec53..49878393eb8 100644 --- a/tests/Unit/Builders/Indexable_Link_Builder/Build_Test.php +++ b/tests/Unit/Builders/Indexable_Link_Builder/Build_Test.php @@ -36,9 +36,6 @@ public static function build_provider() { ', SEO_Links::TYPE_EXTERNAL, false, - false, - false, - false, [], ], [ @@ -48,9 +45,7 @@ public static function build_provider() { ', SEO_Links::TYPE_EXTERNAL_IMAGE, true, - false, - false, - true, + [ 'https://link.com/newly-added-in-post' => 1, 'https://link.com/already-existed-in-post' => 2, @@ -63,9 +58,7 @@ public static function build_provider() { ', SEO_Links::TYPE_EXTERNAL_IMAGE, true, - true, - false, - false, + [ 'https://link.com/newly-added-in-post' => 1, 'https://link.com/already-existed-in-post' => 2, @@ -78,9 +71,7 @@ public static function build_provider() { ', SEO_Links::TYPE_EXTERNAL_IMAGE, true, - false, - true, - true, + [ 'https://link.com/newly-added-in-post' => 1, 'https://link.com/already-existed-in-post' => 2, @@ -93,9 +84,7 @@ public static function build_provider() { ', SEO_Links::TYPE_EXTERNAL_IMAGE, true, - false, - true, - true, + [ 'https://link.com/newly-added-in-post' => 1, 'https://link.com/already-existed-in-post' => 2, @@ -115,17 +104,14 @@ public static function build_provider() { * * @dataProvider build_provider * - * @param string $content The content. - * @param string $link_type The link type. - * @param bool $is_image Whether the link is an image. - * @param bool $ignore_content_scan Whether content scanning should be ignored. - * @param bool $should_content_regex Whether the image id should be extracted with a regex. - * @param bool $should_doc_scan Whether the doc document should be used. - * @param array $images The images that are in the content. + * @param string $content The content. + * @param string $link_type The link type. + * @param bool $is_image Whether the link is an image. + * @param array $images The images that are in the content. * * @return void */ - public function test_build( $content, $link_type, $is_image, $ignore_content_scan, $should_content_regex, $should_doc_scan, $images ) { + public function test_build( $content, $link_type, $is_image, $images ) { $indexable = Mockery::mock( Indexable_Mock::class ); $indexable->id = 1; $indexable->object_id = 2; diff --git a/tests/Unit/Images/Application/Image_Content_Extractor_Test.php b/tests/Unit/Images/Application/Image_Content_Extractor_Test.php new file mode 100644 index 00000000000..d6edc3b18da --- /dev/null +++ b/tests/Unit/Images/Application/Image_Content_Extractor_Test.php @@ -0,0 +1,107 @@ +instance = new Image_Content_Extractor(); + } + + /** + * Data provider to test the build. + * + * @return array> The test data. + */ + public static function build_provider() { + return [ + [ + false, + true, + true, + '', + [ + 'https://link.com/newly-added-in-post' => 8, + ], + ], + [ + true, + false, + false, + '', + [ + 'https://link.com/newly-added-in-post' => 0, + ], + ], + ]; + } + + /** + * Tests the build function. + * + * @covers ::__construct + * @covers ::gather_images + * @covers ::gather_images_wp + * @covers ::gather_images_domdocument + * @covers ::extract_id_of_classes + * + * @dataProvider build_provider + * + * @param bool $ignore_content_scan Whether content scanning should be ignored. + * @param bool $should_content_regex Whether the image id should be extracted with a regex. + * @param bool $should_doc_scan Whether the doc document should be used. + * @param bool $content The content to check. + * @param bool $expected The expected result. + * + * @return void + */ + public function test_gather_images( $ignore_content_scan, $should_content_regex, $should_doc_scan, $content, $expected ) { + if ( $should_doc_scan ) { + Functions\expect( 'apply_filters' ) + ->once() + ->with( 'wpseo_image_attribute_containing_id', 'class' ) + ->andReturn( 'class' ); + + } + if ( $should_content_regex ) { + Functions\expect( 'apply_filters' ) + ->once() + ->with( 'wpseo_extract_id_pattern', '/(?andReturn( '/(?once() + ->with( 'wpseo_force_skip_image_content_parsing', false ) + ->andReturn( true ); + } + + $this->assertEquals( $expected, $this->instance->gather_images( $content ) ); + } +} From f39adbddeb47eb118d252645fde8b437f2c6bc6e Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 14 Aug 2024 14:48:30 +0200 Subject: [PATCH 020/313] Fix wp test --- tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php b/tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php index 98a43e3d1fc..ddf00592d60 100644 --- a/tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php +++ b/tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php @@ -3,6 +3,7 @@ namespace Yoast\WP\SEO\Tests\WP\Helpers; use Yoast\WP\SEO\Helpers\Image_Helper; +use Yoast\WP\SEO\Images\Application\Image_Content_Extractor; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Repositories\SEO_Links_Repository; use Yoast\WP\SEO\Tests\WP\TestCase; @@ -30,7 +31,7 @@ final class Get_Attachment_By_Url_Image_Helper_Test extends TestCase { */ public function set_up(): void { parent::set_up(); - $this->instance = new Image_Helper( \YoastSEO()->classes->get( Indexable_Repository::class ), \YoastSEO()->classes->get( SEO_Links_Repository::class ), \YoastSEO()->helpers->options, \YoastSEO()->helpers->url ); + $this->instance = new Image_Helper( \YoastSEO()->classes->get( Indexable_Repository::class ), \YoastSEO()->classes->get( SEO_Links_Repository::class ), \YoastSEO()->helpers->options, \YoastSEO()->helpers->url,\YoastSEO()->classes->get( Image_Content_Extractor::class ), ); \YoastSEO()->helpers->options->set( 'disable-attachment', true ); global $wpdb; From 088906f0013c3dc8d1c7bd51caed3eb8d86bbaba Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 14 Aug 2024 14:56:54 +0200 Subject: [PATCH 021/313] Remove trailing , --- tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php b/tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php index ddf00592d60..2ed4fe07520 100644 --- a/tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php +++ b/tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php @@ -31,7 +31,7 @@ final class Get_Attachment_By_Url_Image_Helper_Test extends TestCase { */ public function set_up(): void { parent::set_up(); - $this->instance = new Image_Helper( \YoastSEO()->classes->get( Indexable_Repository::class ), \YoastSEO()->classes->get( SEO_Links_Repository::class ), \YoastSEO()->helpers->options, \YoastSEO()->helpers->url,\YoastSEO()->classes->get( Image_Content_Extractor::class ), ); + $this->instance = new Image_Helper( \YoastSEO()->classes->get( Indexable_Repository::class ), \YoastSEO()->classes->get( SEO_Links_Repository::class ), \YoastSEO()->helpers->options, \YoastSEO()->helpers->url, \YoastSEO()->classes->get( Image_Content_Extractor::class ) ); \YoastSEO()->helpers->options->set( 'disable-attachment', true ); global $wpdb; From 67f86292b00a1531f04d9f99bf308ed4886efcfa Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 22 Aug 2024 14:24:18 +0200 Subject: [PATCH 022/313] Revert content scanning to mark image as first content image with an id. --- src/builders/indexable-post-builder.php | 9 --- .../Builders/Indexable_Post_Builder_Test.php | 58 ------------------- 2 files changed, 67 deletions(-) diff --git a/src/builders/indexable-post-builder.php b/src/builders/indexable-post-builder.php index 4837766b6b4..16eef3e1ffe 100644 --- a/src/builders/indexable-post-builder.php +++ b/src/builders/indexable-post-builder.php @@ -377,15 +377,6 @@ protected function find_alternative_image( Indexable $indexable ) { ]; } - $content_image = $this->image->get_post_content_image_id( $indexable->object_id ); - - if ( $content_image !== '' ) { - return [ - 'image_id' => $content_image, - 'source' => 'first-content-image', - ]; - } - $content_image = $this->image->get_post_content_image( $indexable->object_id ); if ( $content_image ) { return [ diff --git a/tests/Unit/Builders/Indexable_Post_Builder_Test.php b/tests/Unit/Builders/Indexable_Post_Builder_Test.php index 9231b223ca4..905264a926c 100644 --- a/tests/Unit/Builders/Indexable_Post_Builder_Test.php +++ b/tests/Unit/Builders/Indexable_Post_Builder_Test.php @@ -634,64 +634,6 @@ public function test_find_alternative_image_from_post_content() { $this->assertEquals( $expected, $actual ); } - /** - * Tests find_alternative_image_id when a image is set on the post, - * but not a featured image. - * - * @covers ::find_alternative_image - * - * @return void - */ - public function test_find_alternative_image_id_from_post_content() { - $this->indexable = Mockery::mock( Indexable::class ); - $this->indexable->orm = Mockery::mock( ORM::class ); - - $this->indexable->orm->allows( 'get' ) - ->with( 'object_sub_type' ) - ->andReturn( 'post' ); - - $this->indexable->orm->allows( 'get' ) - ->with( 'object_id' ) - ->andReturn( 123 ); - - $this->image->allows( 'get_featured_image_id' ) - ->with( 123 ) - ->andReturn( false ); - - $this->image->allows( 'get_gallery_image' ) - ->with( 123 ) - ->andReturn( false ); - - $image_meta = [ - 'width' => 640, - 'height' => 480, - 'url' => 'http://basic.wordpress.test/wp-content/uploads/2020/07/WordPress5.jpg', - 'path' => '/var/www/html/wp-content/uploads/2020/07/WordPress5.jpg', - 'size' => 'full', - 'id' => 13, - 'alt' => '', - 'pixels' => 307200, - 'type' => 'image/jpeg', - ]; - - $this->image->allows( 'get_post_content_image' ) - ->with( 123 ) - ->andReturn( $image_meta ); - - $this->image->allows( 'get_post_content_image_id' ) - ->with( 123 ) - ->andReturn( 13 ); - - $actual = $this->instance->find_alternative_image( $this->indexable ); - - $expected = [ - 'image_id' => $image_meta['id'], - 'source' => 'first-content-image', - ]; - - $this->assertEquals( $expected, $actual ); - } - /** * Tests find_alternative_image when a gallery image is set on the post, * but not a featured image. From f51b18787a1b957605a48159a4de89ec38cca8d1 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Fri, 23 Aug 2024 09:17:09 +0200 Subject: [PATCH 023/313] Add adding image ID to indexable when a image link is updated. --- src/builders/indexable-link-builder.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/builders/indexable-link-builder.php b/src/builders/indexable-link-builder.php index 0a6bc0eab16..6536bc42f9b 100644 --- a/src/builders/indexable-link-builder.php +++ b/src/builders/indexable-link-builder.php @@ -154,6 +154,10 @@ public function build( $indexable, $content ) { return []; } + if ( ! empty( $images ) && $indexable->open_graph_image_source === 'first-content-image' ) { + $this->update_first_content_image( $indexable, $images ); + } + $links = $this->create_links( $indexable, $links, $images ); $this->update_related_indexables( $indexable, $links ); @@ -579,4 +583,24 @@ protected function update_incoming_links_for_related_indexables( $related_indexa $this->indexable_repository->update_incoming_link_count( $count['target_indexable_id'], $count['incoming'] ); } } + + /** + * Updates the image ids when the indexable images are marked as first content image. + * + * @param Indexable $indexable The indexable to change. + * @param array $images The image array. + * + * @return void + */ + public function update_first_content_image( Indexable $indexable, array $images ): void { + $current_first_content_image = $indexable->open_graph_image; + + $first_content_image_url = \key( $images ); + $first_content_image_id = \current( $images ); + + if ( $current_first_content_image === $first_content_image_url ) { + $indexable->open_graph_image_id = $first_content_image_id; + $indexable->twitter_image_id = $first_content_image_id; + } + } } From d4e3e5816891ac9eb335d7db2697c39dc1d3c486 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 28 Aug 2024 10:30:52 +0200 Subject: [PATCH 024/313] Removes now unused methods. --- src/helpers/image-helper.php | 44 ------------------------------------ 1 file changed, 44 deletions(-) diff --git a/src/helpers/image-helper.php b/src/helpers/image-helper.php index 8943426a0ef..f93fc08fc8a 100644 --- a/src/helpers/image-helper.php +++ b/src/helpers/image-helper.php @@ -160,23 +160,6 @@ public function get_featured_image_id( $post_id ) { return \get_post_thumbnail_id( $post_id ); } - /** - * Gets the image ic from the content. - * - * @param int $post_id The post id to extract the images from. - * - * @return string The image url or an empty string when not found. - */ - public function get_post_content_image_id( $post_id ) { - $image_url = $this->get_first_usable_content_image_id_for_post( $post_id ); - - if ( $image_url === null ) { - return ''; - } - - return $image_url; - } - /** * Gets the image ic from the content. * @@ -418,33 +401,6 @@ public function get_attachment_meta_from_settings( $setting ) { return $image_meta; } - /** - * Retrieves the first usable content image_id for a post. - * - * @codeCoverageIgnore - We have to write test when this method contains own code. - * - * @param int $post_id The post id to extract the images from. - * - * @return string|null - */ - protected function get_first_usable_content_image_id_for_post( $post_id ) { - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- To setup the post we need to do this explicitly. - global $post; - $post_backup = $post; - $post = \get_post( $post_id ); - - \setup_postdata( $post ); - $content = \apply_filters( 'the_content', $post->post_content ); - \wp_reset_postdata(); - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- To setup the post we need to do this explicitly. - $post = $post_backup; - - $content = \str_replace( ']]>', ']]>', $content ); - $images = $this->image_content_extractor->gather_images( $content ); - - return \array_shift( $images ); - } - /** * Retrieves the first usable content image for a post. * From a13c31449e05cb443c507288a5bfe29cffbc9f6a Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 28 Aug 2024 10:32:06 +0200 Subject: [PATCH 025/313] Removes now unneeded dependency --- src/helpers/image-helper.php | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/helpers/image-helper.php b/src/helpers/image-helper.php index f93fc08fc8a..cefe7088f28 100644 --- a/src/helpers/image-helper.php +++ b/src/helpers/image-helper.php @@ -3,7 +3,6 @@ namespace Yoast\WP\SEO\Helpers; use WPSEO_Image_Utils; -use Yoast\WP\SEO\Images\Application\Image_Content_Extractor; use Yoast\WP\SEO\Models\SEO_Links; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Repositories\SEO_Links_Repository; @@ -41,13 +40,6 @@ class Image_Helper { */ protected $seo_links_repository; - /** - * The image content extractor. - * - * @var Image_Content_Extractor - */ - protected $image_content_extractor; - /** * The options helper. * @@ -65,24 +57,21 @@ class Image_Helper { /** * Image_Helper constructor. * - * @param Indexable_Repository $indexable_repository The indexable repository. - * @param SEO_Links_Repository $seo_links_repository The SEO Links repository. - * @param Options_Helper $options The options helper. - * @param Url_Helper $url_helper The URL helper. - * @param Image_Content_Extractor $image_content_extractor The image content extractor. + * @param Indexable_Repository $indexable_repository The indexable repository. + * @param SEO_Links_Repository $seo_links_repository The SEO Links repository. + * @param Options_Helper $options The options helper. + * @param Url_Helper $url_helper The URL helper. */ public function __construct( Indexable_Repository $indexable_repository, SEO_Links_Repository $seo_links_repository, Options_Helper $options, - Url_Helper $url_helper, - Image_Content_Extractor $image_content_extractor + Url_Helper $url_helper ) { - $this->indexable_repository = $indexable_repository; - $this->seo_links_repository = $seo_links_repository; - $this->options_helper = $options; - $this->url_helper = $url_helper; - $this->image_content_extractor = $image_content_extractor; + $this->indexable_repository = $indexable_repository; + $this->seo_links_repository = $seo_links_repository; + $this->options_helper = $options; + $this->url_helper = $url_helper; } /** From 97e59025dd8ad4862caa3e6768eeb67931367edb Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 28 Aug 2024 12:41:22 +0200 Subject: [PATCH 026/313] More checks to make sure we only update the first content image when needed. --- composer.json | 2 +- src/builders/indexable-link-builder.php | 10 +++++++--- tests/Unit/Helpers/Image_Helper_Test.php | 11 +---------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index dd95768d495..05ac91adee3 100644 --- a/composer.json +++ b/composer.json @@ -91,7 +91,7 @@ "Yoast\\WP\\SEO\\Composer\\Actions::check_coding_standards" ], "check-cs-thresholds": [ - "@putenv YOASTCS_THRESHOLD_ERRORS=2482", + "@putenv YOASTCS_THRESHOLD_ERRORS=2481", "@putenv YOASTCS_THRESHOLD_WARNINGS=252", "Yoast\\WP\\SEO\\Composer\\Actions::check_cs_thresholds" ], diff --git a/src/builders/indexable-link-builder.php b/src/builders/indexable-link-builder.php index 6536bc42f9b..3819aecf3c7 100644 --- a/src/builders/indexable-link-builder.php +++ b/src/builders/indexable-link-builder.php @@ -154,7 +154,7 @@ public function build( $indexable, $content ) { return []; } - if ( ! empty( $images ) && $indexable->open_graph_image_source === 'first-content-image' ) { + if ( ! empty( $images ) && ( $indexable->open_graph_image_source === 'first-content-image' || $indexable->twitter_image_source === 'first-content-image' ) ) { $this->update_first_content_image( $indexable, $images ); } @@ -599,8 +599,12 @@ public function update_first_content_image( Indexable $indexable, array $images $first_content_image_id = \current( $images ); if ( $current_first_content_image === $first_content_image_url ) { - $indexable->open_graph_image_id = $first_content_image_id; - $indexable->twitter_image_id = $first_content_image_id; + if ( $indexable->open_graph_image_source === 'first-content-image' ) { + $indexable->open_graph_image_id = $first_content_image_id; + } + if ( $indexable->twitter_image_source === 'first-content-image' ) { + $indexable->twitter_image_id = $first_content_image_id; + } } } } diff --git a/tests/Unit/Helpers/Image_Helper_Test.php b/tests/Unit/Helpers/Image_Helper_Test.php index 6f3940c57d5..1a31b936062 100644 --- a/tests/Unit/Helpers/Image_Helper_Test.php +++ b/tests/Unit/Helpers/Image_Helper_Test.php @@ -7,7 +7,6 @@ use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; -use Yoast\WP\SEO\Images\Application\Image_Content_Extractor; use Yoast\WP\SEO\Models\SEO_Links; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Repositories\SEO_Links_Repository; @@ -66,13 +65,6 @@ final class Image_Helper_Test extends TestCase { */ protected $url_helper; - /** - * The Image content extractor instance. - * - * @var Mockery\MockInterface|Image_Content_Extractor - */ - protected $image_content_extractor; - /** * Setup. * @@ -89,9 +81,8 @@ protected function set_up() { $this->indexable_seo_links_repository = Mockery::mock( SEO_Links_Repository::class ); $this->options_helper = Mockery::mock( Options_Helper::class ); $this->url_helper = Mockery::mock( Url_Helper::class ); - $this->image_content_extractor = Mockery::mock( Image_Content_Extractor::class ); - $this->actual_instance = new Image_Helper( $this->indexable_repository, $this->indexable_seo_links_repository, $this->options_helper, $this->url_helper, $this->image_content_extractor ); + $this->actual_instance = new Image_Helper( $this->indexable_repository, $this->indexable_seo_links_repository, $this->options_helper, $this->url_helper ); } /** From f5c46ac3146cd2c5fed75b0a91246e0c25c917b1 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 28 Aug 2024 12:46:57 +0200 Subject: [PATCH 027/313] fix wp test --- tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php b/tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php index 2ed4fe07520..98a43e3d1fc 100644 --- a/tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php +++ b/tests/WP/Helpers/Get_Attachment_By_Url_Image_Helper_Test.php @@ -3,7 +3,6 @@ namespace Yoast\WP\SEO\Tests\WP\Helpers; use Yoast\WP\SEO\Helpers\Image_Helper; -use Yoast\WP\SEO\Images\Application\Image_Content_Extractor; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Repositories\SEO_Links_Repository; use Yoast\WP\SEO\Tests\WP\TestCase; @@ -31,7 +30,7 @@ final class Get_Attachment_By_Url_Image_Helper_Test extends TestCase { */ public function set_up(): void { parent::set_up(); - $this->instance = new Image_Helper( \YoastSEO()->classes->get( Indexable_Repository::class ), \YoastSEO()->classes->get( SEO_Links_Repository::class ), \YoastSEO()->helpers->options, \YoastSEO()->helpers->url, \YoastSEO()->classes->get( Image_Content_Extractor::class ) ); + $this->instance = new Image_Helper( \YoastSEO()->classes->get( Indexable_Repository::class ), \YoastSEO()->classes->get( SEO_Links_Repository::class ), \YoastSEO()->helpers->options, \YoastSEO()->helpers->url ); \YoastSEO()->helpers->options->set( 'disable-attachment', true ); global $wpdb; From e95ed32940e1265b093f25dd229a025d75e0b5be Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Wed, 28 Aug 2024 16:54:52 +0300 Subject: [PATCH 028/313] Revert obsolete annotation change --- src/helpers/image-helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/image-helper.php b/src/helpers/image-helper.php index cefe7088f28..2f6dc77c7b3 100644 --- a/src/helpers/image-helper.php +++ b/src/helpers/image-helper.php @@ -150,7 +150,7 @@ public function get_featured_image_id( $post_id ) { } /** - * Gets the image ic from the content. + * Gets the image url from the content. * * @param int $post_id The post id to extract the images from. * From d1888e102e6383f170599be5c69b9de6cc59a1f6 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 29 Aug 2024 12:58:13 +0200 Subject: [PATCH 029/313] Add more checks. --- src/builders/indexable-link-builder.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/builders/indexable-link-builder.php b/src/builders/indexable-link-builder.php index 3819aecf3c7..4fbe978c15e 100644 --- a/src/builders/indexable-link-builder.php +++ b/src/builders/indexable-link-builder.php @@ -593,18 +593,17 @@ protected function update_incoming_links_for_related_indexables( $related_indexa * @return void */ public function update_first_content_image( Indexable $indexable, array $images ): void { - $current_first_content_image = $indexable->open_graph_image; + $current_open_graph_image = $indexable->open_graph_image; + $current_twitter_image = $indexable->twitter_image; $first_content_image_url = \key( $images ); $first_content_image_id = \current( $images ); - if ( $current_first_content_image === $first_content_image_url ) { - if ( $indexable->open_graph_image_source === 'first-content-image' ) { - $indexable->open_graph_image_id = $first_content_image_id; - } - if ( $indexable->twitter_image_source === 'first-content-image' ) { - $indexable->twitter_image_id = $first_content_image_id; - } + if ( $indexable->open_graph_image_source === 'first-content-image' && $current_open_graph_image === $first_content_image_url ) { + $indexable->open_graph_image_id = $first_content_image_id; + } + if ( $indexable->twitter_image_source === 'first-content-image' && $current_twitter_image === $first_content_image_url ) { + $indexable->twitter_image_id = $first_content_image_id; } } } From 80f66f6389ff59786c1fff0ca6477b06ae1b47c6 Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Tue, 3 Sep 2024 12:44:28 +0200 Subject: [PATCH 030/313] Simplifies overriding assessment configurations --- ...eadingDistributionTooLongAssessmentSpec.js | 6 ++-- .../assessors/cornerstone/contentAssessor.js | 35 +++++------------- .../cornerstone/relatedKeywordAssessor.js | 36 +++++-------------- .../assessors/cornerstone/seoAssessor.js | 20 +++++------ .../assessors/relatedKeywordAssessor.js | 4 +-- 5 files changed, 33 insertions(+), 68 deletions(-) diff --git a/packages/yoastseo/spec/scoring/assessments/readability/SubheadingDistributionTooLongAssessmentSpec.js b/packages/yoastseo/spec/scoring/assessments/readability/SubheadingDistributionTooLongAssessmentSpec.js index aee869dcfd9..0139b6569f1 100644 --- a/packages/yoastseo/spec/scoring/assessments/readability/SubheadingDistributionTooLongAssessmentSpec.js +++ b/packages/yoastseo/spec/scoring/assessments/readability/SubheadingDistributionTooLongAssessmentSpec.js @@ -3,7 +3,7 @@ import SubheadingDistributionTooLong from "../../../../src/scoring/assessments/r import Paper from "../../../../src/values/Paper.js"; import Factory from "../../../../src/helpers/factory.js"; import Mark from "../../../../src/values/Mark.js"; -import CornerStoneContentAssessor from "../../../../src/scoring/assessors/cornerstone/contentAssessor.js"; +import CornerstoneContentAssessor from "../../../../src/scoring/assessors/cornerstone/contentAssessor.js"; import ProductCornerstoneContentAssessor from "../../../../src/scoring/assessors/productPages/cornerstone/contentAssessor.js"; import DefaultResearcher from "../../../../src/languageProcessing/languages/_default/Researcher.js"; import EnglishResearcher from "../../../../src/languageProcessing/languages/en/Researcher.js"; @@ -583,7 +583,7 @@ describe( "Language-specific configuration for specific types of content is used expect( assessment._config.farTooMany ).toEqual( japaneseConfig.defaultParameters.farTooMany ); } ); - let cornerStoneContentAssessor = new CornerStoneContentAssessor( englishResearcher ); + let cornerStoneContentAssessor = new CornerstoneContentAssessor( englishResearcher ); let productCornerstoneContentAssessor = new ProductCornerstoneContentAssessor( englishResearcher, mockOptions ); [ cornerStoneContentAssessor, productCornerstoneContentAssessor ].forEach( assessor => { @@ -617,7 +617,7 @@ describe( "Language-specific configuration for specific types of content is used } ); } ); - cornerStoneContentAssessor = new CornerStoneContentAssessor( japaneseResearcher ); + cornerStoneContentAssessor = new CornerstoneContentAssessor( japaneseResearcher ); productCornerstoneContentAssessor = new ProductCornerstoneContentAssessor( japaneseResearcher, mockOptions ); [ cornerStoneContentAssessor, productCornerstoneContentAssessor ].forEach( assessor => { diff --git a/packages/yoastseo/src/scoring/assessors/cornerstone/contentAssessor.js b/packages/yoastseo/src/scoring/assessors/cornerstone/contentAssessor.js index a5741a1fe38..aa52faa1bae 100644 --- a/packages/yoastseo/src/scoring/assessors/cornerstone/contentAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/cornerstone/contentAssessor.js @@ -1,16 +1,11 @@ import ContentAssessor from "../contentAssessor"; -import ParagraphTooLong from "../../assessments/readability/ParagraphTooLongAssessment.js"; import SentenceLengthInText from "../../assessments/readability/SentenceLengthInTextAssessment.js"; import SubheadingDistributionTooLong from "../../assessments/readability/SubheadingDistributionTooLongAssessment.js"; -import TransitionWords from "../../assessments/readability/TransitionWordsAssessment.js"; -import PassiveVoice from "../../assessments/readability/PassiveVoiceAssessment.js"; -import SentenceBeginnings from "../../assessments/readability/SentenceBeginningsAssessment.js"; -import TextPresence from "../../assessments/readability/TextPresenceAssessment.js"; /** * The CornerStoneContentAssessor class is used for the readability analysis on cornerstone content. */ -export default class CornerStoneContentAssessor extends ContentAssessor { +export default class CornerstoneContentAssessor extends ContentAssessor { /** * Creates a new CornerStoneContentAssessor instance. * @param {Researcher} researcher The researcher to use. @@ -20,25 +15,13 @@ export default class CornerStoneContentAssessor extends ContentAssessor { super( researcher, options ); this.type = "cornerstoneContentAssessor"; - this._assessments = [ - new SubheadingDistributionTooLong( { - parameters: { - slightlyTooMany: 250, - farTooMany: 300, - recommendedMaximumLength: 250, - }, - applicableIfTextLongerThan: 250, - cornerstoneContent: true, - } ), - new ParagraphTooLong(), - new SentenceLengthInText( { - slightlyTooMany: 20, - farTooMany: 25, - }, true ), - new TransitionWords(), - new PassiveVoice(), - new TextPresence(), - new SentenceBeginnings(), - ]; + this.addAssessment( "subheadingsTooLong", new SubheadingDistributionTooLong( { + parameters: { slightlyTooMany: 250, farTooMany: 300, recommendedMaximumLength: 250 }, + applicableIfTextLongerThan: 250, + cornerstoneContent: true, + } ) ); + this.addAssessment( "textSentenceLength", new SentenceLengthInText( { + slightlyTooMany: 20, farTooMany: 25 }, true ) + ); } } diff --git a/packages/yoastseo/src/scoring/assessors/cornerstone/relatedKeywordAssessor.js b/packages/yoastseo/src/scoring/assessors/cornerstone/relatedKeywordAssessor.js index 667fa7739cd..9f0a2e8fe67 100644 --- a/packages/yoastseo/src/scoring/assessors/cornerstone/relatedKeywordAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/cornerstone/relatedKeywordAssessor.js @@ -1,39 +1,21 @@ -import Assessor from "../assessor.js"; -import IntroductionKeyword from "../../assessments/seo/IntroductionKeywordAssessment.js"; -import KeyphraseLength from "../../assessments/seo/KeyphraseLengthAssessment.js"; -import KeyphraseDensityAssessment from "../../assessments/seo/KeywordDensityAssessment.js"; -import MetaDescriptionKeyword from "../../assessments/seo/MetaDescriptionKeywordAssessment.js"; -import ImageKeyphrase from "../../assessments/seo/KeyphraseInImageTextAssessment"; -import TextCompetingLinks from "../../assessments/seo/TextCompetingLinksAssessment.js"; -import FunctionWordsInKeyphrase from "../../assessments/seo/FunctionWordsInKeyphraseAssessment.js"; +import RelatedKeywordAssessor from "../relatedKeywordAssessor"; +import KeyphraseInImagesAssessment from "../../assessments/seo/KeyphraseInImageTextAssessment"; /** - * The relatedKeywordAssessor class is used for the related keyword analysis for cornerstone content. + * The CornerstoneRelatedKeywordAssessor class is used for the related keyword analysis for cornerstone content. */ -export default class relatedKeywordAssessor extends Assessor { +export default class CornerstoneRelatedKeywordAssessor extends RelatedKeywordAssessor { /** - * Creates a new relatedKeywordAssessor instance. + * Creates a new CornerstoneRelatedKeywordAssessor instance. * @param {Researcher} researcher The researcher to use. * @param {Object} [options] The assessor options. */ constructor( researcher, options ) { super( researcher, options ); - this.type = "relatedKeywordAssessor"; + this.type = "cornerstoneRelatedKeywordAssessor"; - this._assessments = [ - new IntroductionKeyword(), - new KeyphraseLength( { isRelatedKeyphrase: true } ), - new KeyphraseDensityAssessment(), - new MetaDescriptionKeyword(), - new TextCompetingLinks(), - new FunctionWordsInKeyphrase(), - new ImageKeyphrase( { - scores: { - withAltNonKeyword: 3, - withAlt: 3, - noAlt: 3, - }, - } ), - ]; + this.addAssessment( "imageKeyphrase", new KeyphraseInImagesAssessment( { + scores: { withAltNonKeyword: 3, withAlt: 3, noAlt: 3 }, + } ) ); } } diff --git a/packages/yoastseo/src/scoring/assessors/cornerstone/seoAssessor.js b/packages/yoastseo/src/scoring/assessors/cornerstone/seoAssessor.js index 82859a0fb48..86e78ba123b 100644 --- a/packages/yoastseo/src/scoring/assessors/cornerstone/seoAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/cornerstone/seoAssessor.js @@ -1,9 +1,9 @@ import SEOAssessor from "../seoAssessor"; -import MetaDescriptionLength from "../../assessments/seo/MetaDescriptionLengthAssessment"; -import ImageKeyphrase from "../../assessments/seo/KeyphraseInImageTextAssessment"; -import TextLength from "../../assessments/seo/TextLengthAssessment"; -import OutboundLinks from "../../assessments/seo/OutboundLinksAssessment"; -import TitleWidth from "../../assessments/seo/PageTitleWidthAssessment"; +import MetaDescriptionLengthAssessment from "../../assessments/seo/MetaDescriptionLengthAssessment"; +import KeyphraseInImagesAssessment from "../../assessments/seo/KeyphraseInImageTextAssessment"; +import TextLengthAssessment from "../../assessments/seo/TextLengthAssessment"; +import OutboundLinksAssessment from "../../assessments/seo/OutboundLinksAssessment"; +import PageTitleWidthAssessment from "../../assessments/seo/PageTitleWidthAssessment"; import SlugKeywordAssessment from "../../assessments/seo/UrlKeywordAssessment"; /** @@ -19,23 +19,23 @@ export default class CornerstoneSEOAssessor extends SEOAssessor { super( researcher, options ); this.type = "cornerstoneSEOAssessor"; - this.addAssessment( "metaDescriptionLength", new MetaDescriptionLength( { + this.addAssessment( "metaDescriptionLength", new MetaDescriptionLengthAssessment( { scores: { tooLong: 3, tooShort: 3 }, } ) ); - this.addAssessment( "imageKeyphrase", new ImageKeyphrase( { + this.addAssessment( "imageKeyphrase", new KeyphraseInImagesAssessment( { scores: { withAltNonKeyword: 3, withAlt: 3, noAlt: 3 }, } ) ); - this.addAssessment( "textLength", new TextLength( { + this.addAssessment( "textLength", new TextLengthAssessment( { recommendedMinimum: 900, slightlyBelowMinimum: 400, belowMinimum: 300, scores: { belowMinimum: -20, farBelowMinimum: -20 }, cornerstoneContent: true, } ) ); - this.addAssessment( "externalLinks", new OutboundLinks( { + this.addAssessment( "externalLinks", new OutboundLinksAssessment( { scores: { noLinks: 3 }, } ) ); - this.addAssessment( "titleWidth", new TitleWidth( { + this.addAssessment( "titleWidth", new PageTitleWidthAssessment( { scores: { widthTooShort: 9 }, }, true ) ); this.addAssessment( "slugKeyword", new SlugKeywordAssessment( { diff --git a/packages/yoastseo/src/scoring/assessors/relatedKeywordAssessor.js b/packages/yoastseo/src/scoring/assessors/relatedKeywordAssessor.js index 9803da61248..2a3c84048a3 100644 --- a/packages/yoastseo/src/scoring/assessors/relatedKeywordAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/relatedKeywordAssessor.js @@ -10,9 +10,9 @@ import ImageKeyphrase from "../assessments/seo/KeyphraseInImageTextAssessment"; /** * The relatedKeywordAssessor class is used for the related keyword analysis. */ -export default class relatedKeywordAssessor extends Assessor { +export default class RelatedKeywordAssessor extends Assessor { /** - * Creates a new relatedKeywordAssessor instance. + * Creates a new RelatedKeywordAssessor instance. * @param {Researcher} researcher The researcher to use. * @param {Object} [options] The assessor options. */ From 0f95777b1a4a09e726f49d6b1cbc37e20dfd2d79 Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Tue, 3 Sep 2024 13:44:39 +0200 Subject: [PATCH 031/313] Improves some JSDoc --- .../yoastseo/src/scoring/assessors/assessor.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/yoastseo/src/scoring/assessors/assessor.js b/packages/yoastseo/src/scoring/assessors/assessor.js index 6679bf8658b..23862566234 100644 --- a/packages/yoastseo/src/scoring/assessors/assessor.js +++ b/packages/yoastseo/src/scoring/assessors/assessor.js @@ -47,7 +47,7 @@ class Assessor { /** * Gets all available assessments. - * @returns {object} assessment + * @returns {Assessment[]} assessment */ getAvailableAssessments() { return this._assessments; @@ -56,7 +56,7 @@ class Assessor { /** * Checks whether the Assessment is applicable. * - * @param {Object} assessment The Assessment object that needs to be checked. + * @param {Assessment} assessment The Assessment object that needs to be checked. * @param {Paper} paper The Paper object to check against. * @param {Researcher} [researcher] The Researcher object containing additional information. * @returns {boolean} Whether or not the Assessment is applicable. @@ -72,7 +72,7 @@ class Assessor { /** * Determines whether an assessment has a marker. * - * @param {Object} assessment The assessment to check for. + * @param {Assessment} assessment The assessment to check for. * @returns {boolean} Whether or not the assessment has a marker. */ hasMarker( assessment ) { @@ -100,7 +100,7 @@ class Assessor { /** * Returns the marker for a given assessment, composes the specific marker with the assessment getMarks function. * - * @param {Object} assessment The assessment for which we are retrieving the composed marker. + * @param {Assessment} assessment The assessment for which we are retrieving the composed marker. * @param {Paper} paper The paper to retrieve the marker for. * @param {Researcher} researcher The researcher for the paper. * @returns {Function} A function that can mark the given paper according to the given assessment. @@ -166,7 +166,7 @@ class Assessor { * * @param {Paper} paper The paper to pass to the assessment. * @param {Researcher} researcher The researcher to pass to the assessment. - * @param {Object} assessment The assessment to execute. + * @param {Assessment} assessment The assessment to execute. * @returns {AssessmentResult} The result of the assessment. */ executeAssessment( paper, researcher, assessment ) { @@ -205,7 +205,7 @@ class Assessor { /** * Filters out all assessment results that have no score and no text. * - * @returns {Array} The array with all the valid assessments. + * @returns {AssessmentResult[]} The array with all the valid assessments. */ getValidResults() { return filter( this.results, function( result ) { @@ -216,7 +216,7 @@ class Assessor { /** * Returns if an assessmentResult is valid. * - * @param {object} assessmentResult The assessmentResult to validate. + * @param {AssessmentResult} assessmentResult The assessmentResult to validate. * @returns {boolean} whether or not the result is valid. */ isValidResult( assessmentResult ) { @@ -241,7 +241,7 @@ class Assessor { * Register an assessment to add it to the internal assessments object. * * @param {string} name The name of the assessment. - * @param {object} assessment The object containing function to run as an assessment and it's requirements. + * @param {Assessment} assessment The object containing function to run as an assessment and it's requirements. * @returns {boolean} Whether registering the assessment was successful. */ addAssessment( name, assessment ) { @@ -288,7 +288,7 @@ class Assessor { /** * Checks which of the available assessments are applicable and returns an array with applicable assessments. * - * @returns {Array} The array with applicable assessments. + * @returns {Assessment[]} The array with applicable assessments. */ getApplicableAssessments() { const availableAssessments = this.getAvailableAssessments(); From 19092d2c02623e89b38c51d50c463174197db41b Mon Sep 17 00:00:00 2001 From: Enrico Battocchi Date: Tue, 3 Sep 2024 14:30:26 +0200 Subject: [PATCH 032/313] Fix broken links in the readme --- readme.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.txt b/readme.txt index 40ab72083d4..682021a5d02 100644 --- a/readme.txt +++ b/readme.txt @@ -30,7 +30,7 @@ Empower search engines to fully understand your website using our Schema.org str Yoast SEO offers comprehensive analysis tools that help elevate your content's SEO and readability. Get powerful insights and actionable recommendations to craft helpful content that resonates with readers and search engines. -**Premium Yoast AI features** Get suggestions for your titles and descriptions at the click of a button. The [Yoast AI features] (https://yoa.st/51c) save you time and optimize for higher click-through-rates. +**Premium Yoast AI features** Get suggestions for your titles and descriptions at the click of a button. The [Yoast AI features](https://yoa.st/51c) save you time and optimize for higher click-through-rates. * Yoast AI Generate enables users to generate meta descriptions and titles for your pages, blog posts and social posts. Great! Even better, when you also have [Yoast WooCommerce SEO](https://yoa.st/3rh), you can recieve suggestions for product SEO titles and descriptions too! The best part, if you don't like the 5 suggestions, you can generate five more at a click. * Yoast AI Optimize helps you optimize existing content for search engines. Optimize three of the assessments in the Yoast SEO Analysis; Keyphrase in introduction, Keyphrase distribution and Keyphrase density, with easy dismiss or apply options. @@ -132,7 +132,7 @@ If you're looking for a structured learning path, our [Yoast SEO academy](https: Not only do you get many additional benefits by upgrading to [Yoast SEO Premium](https://yoa.st/1v8), but you'll also get 24/7 personalized support that takes away your worry. -* Unlock our [AI features] (https://yoa.st/51c); Yoast AI Optimize and Yoast AI Generate. Perfect for marketing professionals, freelance writers, and content strategists, Yoast AI features enable customers of all technical levels to apply SEO best practice to their content at the click of a button. +* Unlock our [AI features](https://yoa.st/51c); Yoast AI Optimize and Yoast AI Generate. Perfect for marketing professionals, freelance writers, and content strategists, Yoast AI features enable customers of all technical levels to apply SEO best practice to their content at the click of a button. * Optimize for up to five keyword synonyms by adding variants. Add up to four related synonyms of your keyword to expand your possibilities. You get the full SEO analysis for each. From 56baaa4a9ac70cb95cc9a258d712eaf368b7f9ce Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 4 Sep 2024 11:31:33 +0300 Subject: [PATCH 033/313] Add isWoocommerceActive to editors directory --- .../framework/site/post-site-information.php | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/editors/framework/site/post-site-information.php b/src/editors/framework/site/post-site-information.php index 4882642aa7a..c0f091eeb6d 100644 --- a/src/editors/framework/site/post-site-information.php +++ b/src/editors/framework/site/post-site-information.php @@ -3,6 +3,7 @@ namespace Yoast\WP\SEO\Editors\Framework\Site; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; +use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; @@ -35,6 +36,13 @@ class Post_Site_Information extends Base_Site_Information { */ private $promotion_manager; + /** + * The WooCommerce conditional. + * + * @var WooCommerce_Conditional $woocommerce_conditional + */ + protected $woocommerce_conditional; + /** * Constructs the class. * @@ -45,6 +53,7 @@ class Post_Site_Information extends Base_Site_Information { * @param Meta_Surface $meta The meta surface. * @param Product_Helper $product_helper The product helper. * @param Alert_Dismissal_Action $alert_dismissal_action The alert dismissal action. + * @param WooCommerce_Conditional $woocommerce_conditional The WooCommerce conditional. * * @return void */ @@ -54,11 +63,13 @@ public function __construct( Wistia_Embed_Permission_Repository $wistia_embed_permission_repository, Meta_Surface $meta, Product_Helper $product_helper, - Alert_Dismissal_Action $alert_dismissal_action + Alert_Dismissal_Action $alert_dismissal_action, + WooCommerce_Conditional $woocommerce_conditional ) { parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper ); - $this->promotion_manager = $promotion_manager; - $this->alert_dismissal_action = $alert_dismissal_action; + $this->promotion_manager = $promotion_manager; + $this->alert_dismissal_action = $alert_dismissal_action; + $this->woocommerce_conditional = $woocommerce_conditional; } /** @@ -85,6 +96,8 @@ public function get_legacy_site_information(): array { 'currentPromotions' => $this->promotion_manager->get_current_promotions(), 'webinarIntroBlockEditorUrl' => $this->short_link_helper->get( 'https://yoa.st/webinar-intro-block-editor' ), 'blackFridayBlockEditorUrl' => ( $this->promotion_manager->is( 'black-friday-2023-checklist' ) ) ? $this->short_link_helper->get( 'https://yoa.st/black-friday-checklist' ) : '', + 'search_url' => $this->search_url(), + 'isWooCommerceActive' => $this->woocommerce_conditional->is_met(), 'metabox' => [ 'search_url' => $this->search_url(), 'post_edit_url' => $this->edit_url(), @@ -111,6 +124,7 @@ public function get_site_information(): array { 'search_url' => $this->search_url(), 'post_edit_url' => $this->edit_url(), 'base_url' => $this->base_url_for_js(), + 'isWooCommerceActive' => $this->woocommerce_conditional->is_met(), ]; return \array_merge( $data, parent::get_site_information() ); From 8e6784a133bb076bc06a0a1b576c995a41227bc9 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 4 Sep 2024 11:32:53 +0300 Subject: [PATCH 034/313] removes isWoocommerceActive from metabox and elementor --- admin/metabox/class-metabox.php | 8 ++------ src/integrations/third-party/elementor.php | 5 +---- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/admin/metabox/class-metabox.php b/admin/metabox/class-metabox.php index 1066a560ef6..1bf3ca7d972 100644 --- a/admin/metabox/class-metabox.php +++ b/admin/metabox/class-metabox.php @@ -7,7 +7,6 @@ use Yoast\WP\SEO\Conditionals\Third_Party\Jetpack_Boost_Active_Conditional; use Yoast\WP\SEO\Conditionals\Third_Party\Jetpack_Boost_Not_Premium_Conditional; -use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Editors\Application\Site\Website_Information_Repository; use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter; use Yoast\WP\SEO\Presenters\Admin\Meta_Fields_Presenter; @@ -879,10 +878,8 @@ public function enqueue() { 'log_level' => WPSEO_Utils::get_analysis_worker_log_level(), ]; - $woocommerce_conditional = new WooCommerce_Conditional(); - $woocommerce_active = $woocommerce_conditional->is_met(); - $addon_manager = new WPSEO_Addon_Manager(); - $woocommerce_seo_active = is_plugin_active( $addon_manager->get_plugin_file( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) ); + $addon_manager = new WPSEO_Addon_Manager(); + $woocommerce_seo_active = is_plugin_active( $addon_manager->get_plugin_file( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) ); $script_data = [ 'metabox' => $this->get_metabox_script_data(), @@ -900,7 +897,6 @@ public function enqueue() { 'isJetpackBoostActive' => ( $is_block_editor ) ? YoastSEO()->classes->get( Jetpack_Boost_Active_Conditional::class )->is_met() : false, 'isJetpackBoostNotPremium' => ( $is_block_editor ) ? YoastSEO()->classes->get( Jetpack_Boost_Not_Premium_Conditional::class )->is_met() : false, 'isWooCommerceSeoActive' => $woocommerce_seo_active, - 'isWooCommerceActive' => $woocommerce_active, ]; /** diff --git a/src/integrations/third-party/elementor.php b/src/integrations/third-party/elementor.php index e2405da7262..33327705745 100644 --- a/src/integrations/third-party/elementor.php +++ b/src/integrations/third-party/elementor.php @@ -17,7 +17,6 @@ use WPSEO_Shortlinker; use WPSEO_Utils; use Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Edit_Conditional; -use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Editors\Application\Site\Website_Information_Repository; use Yoast\WP\SEO\Elementor\Infrastructure\Request_Post; use Yoast\WP\SEO\Helpers\Capability_Helper; @@ -409,8 +408,7 @@ public function enqueue() { 'enabled_features' => WPSEO_Utils::retrieve_enabled_features(), ]; - $woocommerce_conditional = new WooCommerce_Conditional(); - $permalink = $this->get_permalink(); + $permalink = $this->get_permalink(); $script_data = [ 'metabox' => $this->get_metabox_script_data( $permalink ), @@ -418,7 +416,6 @@ public function enqueue() { 'isPost' => true, 'isBlockEditor' => WP_Screen::get()->is_block_editor(), 'isElementorEditor' => true, - 'isWooCommerceActive' => $woocommerce_conditional->is_met(), 'postStatus' => \get_post_status( $post_id ), 'postType' => \get_post_type( $post_id ), 'analysis' => [ From d7258611e64e8c8bc13f6ee0f3c3eb13f5155401 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 4 Sep 2024 11:49:18 +0300 Subject: [PATCH 035/313] fix tests --- .../framework/site/post-site-information.php | 1 - .../Site/Post_Site_Information_Test.php | 32 +++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/editors/framework/site/post-site-information.php b/src/editors/framework/site/post-site-information.php index c0f091eeb6d..bd04e597aa3 100644 --- a/src/editors/framework/site/post-site-information.php +++ b/src/editors/framework/site/post-site-information.php @@ -96,7 +96,6 @@ public function get_legacy_site_information(): array { 'currentPromotions' => $this->promotion_manager->get_current_promotions(), 'webinarIntroBlockEditorUrl' => $this->short_link_helper->get( 'https://yoa.st/webinar-intro-block-editor' ), 'blackFridayBlockEditorUrl' => ( $this->promotion_manager->is( 'black-friday-2023-checklist' ) ) ? $this->short_link_helper->get( 'https://yoa.st/black-friday-checklist' ) : '', - 'search_url' => $this->search_url(), 'isWooCommerceActive' => $this->woocommerce_conditional->is_met(), 'metabox' => [ 'search_url' => $this->search_url(), diff --git a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php index c3ecc881b8f..e2d5b083cfd 100644 --- a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php @@ -6,6 +6,7 @@ use Brain\Monkey; use Mockery; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; +use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Editors\Framework\Site\Post_Site_Information; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; @@ -68,6 +69,13 @@ final class Post_Site_Information_Test extends TestCase { */ private $product_helper; + /** + * The WooCommerce conditional. + * + * @var WooCommerce_Conditional $woocommerce_conditional + */ + protected $woocommerce_conditional; + /** * The Post_Site_Information container. * @@ -82,14 +90,15 @@ final class Post_Site_Information_Test extends TestCase { */ protected function set_up() { parent::set_up(); - $this->promotion_manager = Mockery::mock( Promotion_Manager::class ); - $this->short_link_helper = Mockery::mock( Short_Link_Helper::class ); - $this->wistia_embed_repo = Mockery::mock( Wistia_Embed_Permission_Repository::class ); - $this->meta_surface = Mockery::mock( Meta_Surface::class ); - $this->product_helper = Mockery::mock( Product_Helper::class ); - $this->alert_dismissal_action = Mockery::mock( Alert_Dismissal_Action::class ); - - $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action ); + $this->promotion_manager = Mockery::mock( Promotion_Manager::class ); + $this->short_link_helper = Mockery::mock( Short_Link_Helper::class ); + $this->wistia_embed_repo = Mockery::mock( Wistia_Embed_Permission_Repository::class ); + $this->meta_surface = Mockery::mock( Meta_Surface::class ); + $this->product_helper = Mockery::mock( Product_Helper::class ); + $this->alert_dismissal_action = Mockery::mock( Alert_Dismissal_Action::class ); + $this->woocommerce_conditional = Mockery::mock( WooCommerce_Conditional::class ); + + $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action, $this->woocommerce_conditional ); $this->instance->set_permalink( 'perma' ); $this->set_mocks(); } @@ -117,6 +126,7 @@ public function test_legacy_site_information() { ], 'webinarIntroBlockEditorUrl' => 'https://expl.c', 'blackFridayBlockEditorUrl' => '', + 'isWooCommerceActive' => '', 'metabox' => [ 'search_url' => 'https://example.org', 'post_edit_url' => 'https://example.org', @@ -136,7 +146,7 @@ public function test_legacy_site_information() { ], 'pluginUrl' => '/location', 'wistiaEmbedPermission' => true, - + 'isWooCommerceActive' => '', ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); @@ -146,6 +156,7 @@ public function test_legacy_site_information() { $this->promotion_manager->expects( 'get_current_promotions' )->andReturn( [ 'the promotion', 'another one' ] ); $this->promotion_manager->expects( 'is' )->andReturnFalse(); $this->short_link_helper->expects( 'get' )->andReturn( 'https://expl.c' ); + $this->woocommerce_conditional->expects( 'is_met' )->andReturn( '' ); $this->assertSame( $expected, $this->instance->get_legacy_site_information() ); } @@ -177,7 +188,7 @@ public function test_site_information() { 'search_url' => 'https://example.org', 'post_edit_url' => 'https://example.org', 'base_url' => 'https://example.org', - + 'isWooCommerceActive' => '1', 'adminUrl' => 'https://example.org', 'linkParams' => [ 'param', @@ -201,6 +212,7 @@ public function test_site_information() { $this->promotion_manager->expects( 'get_current_promotions' )->andReturn( [ 'the promotion', 'another one' ] ); $this->promotion_manager->expects( 'is' )->andReturnFalse(); $this->short_link_helper->expects( 'get' )->andReturn( 'https://expl.c' ); + $this->woocommerce_conditional->expects( 'is_met' )->andReturn( '1' ); $this->assertSame( $expected, $this->instance->get_site_information() ); } From 4891b4ef87b404288d66897975543665a6e83500 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 4 Sep 2024 13:09:50 +0300 Subject: [PATCH 036/313] fix wp tests --- .../Site/Post_Site_Information_Test.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php index a7034a1c5c5..c96860eaba1 100644 --- a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php @@ -5,6 +5,7 @@ use Mockery; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; +use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Editors\Framework\Site\Post_Site_Information; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; @@ -64,6 +65,13 @@ final class Post_Site_Information_Test extends TestCase { */ private $product_helper; + /** + * The WooCommerce conditional. + * + * @var WooCommerce_Conditional $woocommerce_conditional + */ + protected $woocommerce_conditional; + /** * The Post_Site_Information container. * @@ -82,11 +90,12 @@ public function set_up() { $this->short_link_helper = \YoastSEO()->helpers->short_link; $this->wistia_embed_repo = Mockery::mock( Wistia_Embed_Permission_Repository::class ); $this->wistia_embed_repo->expects( 'get_value_for_user' )->with( 0 )->andReturnTrue(); - $this->meta_surface = \YoastSEO()->meta; - $this->product_helper = \YoastSEO()->helpers->product; - $this->alert_dismissal_action = \YoastSEO()->classes->get( Alert_Dismissal_Action::class ); + $this->meta_surface = \YoastSEO()->meta; + $this->product_helper = \YoastSEO()->helpers->product; + $this->alert_dismissal_action = \YoastSEO()->classes->get( Alert_Dismissal_Action::class ); + $this->woocommerce_conditional = \YoastSEO()->classes->get( WooCommerce_Conditional::class ); - $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action ); + $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action, $this->woocommerce_conditional ); $this->instance->set_permalink( 'perma' ); } @@ -108,6 +117,7 @@ public function test_legacy_site_information() { 'currentPromotions' => [], 'webinarIntroBlockEditorUrl' => $this->short_link_helper->get( 'https://yoa.st/webinar-intro-block-editor' ), 'blackFridayBlockEditorUrl' => '', + 'isWooCommerceActive' => false, 'metabox' => [ 'search_url' => 'http://example.org/wp-admin/edit.php?seo_kw_filter={keyword}', @@ -150,6 +160,7 @@ public function test_site_information() { 'search_url' => 'http://example.org/wp-admin/edit.php?seo_kw_filter={keyword}', 'post_edit_url' => 'http://example.org/wp-admin/post.php?post={id}&action=edit', 'base_url' => 'http://example.org/', + 'isWooCommerceActive' => false, 'adminUrl' => 'http://example.org/wp-admin/admin.php', 'linkParams' => $this->short_link_helper->get_query_params(), 'pluginUrl' => 'http://example.org/wp-content/plugins/wordpress-seo', From d8a7ab237cf7d2654cc7eefbf1defdb55878e1fb Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 4 Sep 2024 13:29:45 +0300 Subject: [PATCH 037/313] php fix-cs --- tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php index e2d5b083cfd..53d015981fb 100644 --- a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php @@ -146,7 +146,6 @@ public function test_legacy_site_information() { ], 'pluginUrl' => '/location', 'wistiaEmbedPermission' => true, - 'isWooCommerceActive' => '', ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); From 9a5b24050676dddc8690ecc62300c9ca72b69ae6 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 4 Sep 2024 13:30:09 +0300 Subject: [PATCH 038/313] remove unused isWooCommerceActive property --- admin/class-config.php | 1 - 1 file changed, 1 deletion(-) diff --git a/admin/class-config.php b/admin/class-config.php index 0f8ce558885..408decb7a1f 100644 --- a/admin/class-config.php +++ b/admin/class-config.php @@ -98,7 +98,6 @@ public function config_page_scripts() { 'dismissedAlerts' => $dismissed_alerts, 'isRtl' => is_rtl(), 'isPremium' => YoastSEO()->helpers->product->is_premium(), - 'isWooCommerceActive' => $woocommerce_conditional->is_met(), 'currentPromotions' => YoastSEO()->classes->get( Promotion_Manager::class )->get_current_promotions(), 'webinarIntroSettingsUrl' => WPSEO_Shortlinker::get( 'https://yoa.st/webinar-intro-settings' ), 'webinarIntroFirstTimeConfigUrl' => $this->get_webinar_shortlink(), From 924d33eaf7a2b30db3eaa14f0ef190fd4894b6f8 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 4 Sep 2024 13:33:26 +0300 Subject: [PATCH 039/313] remove unused variables --- admin/class-config.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/admin/class-config.php b/admin/class-config.php index 408decb7a1f..0c8b468add4 100644 --- a/admin/class-config.php +++ b/admin/class-config.php @@ -6,7 +6,6 @@ */ use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; -use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Integrations\Academy_Integration; use Yoast\WP\SEO\Integrations\Settings_Integration; use Yoast\WP\SEO\Integrations\Support_Integration; @@ -91,7 +90,6 @@ public function config_page_scripts() { $alert_dismissal_action = YoastSEO()->classes->get( Alert_Dismissal_Action::class ); $dismissed_alerts = $alert_dismissal_action->all_dismissed(); - $woocommerce_conditional = new WooCommerce_Conditional(); $script_data = [ 'userLanguageCode' => WPSEO_Language_Utils::get_language( get_user_locale() ), From b5a67e3c41fddcef80fdc209c437e7e90d534311 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 4 Sep 2024 13:36:14 +0300 Subject: [PATCH 040/313] php fix-cs --- admin/class-config.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/class-config.php b/admin/class-config.php index 0c8b468add4..351cedc12d0 100644 --- a/admin/class-config.php +++ b/admin/class-config.php @@ -88,8 +88,8 @@ public function config_page_scripts() { wp_enqueue_script( 'dashboard' ); wp_enqueue_script( 'thickbox' ); - $alert_dismissal_action = YoastSEO()->classes->get( Alert_Dismissal_Action::class ); - $dismissed_alerts = $alert_dismissal_action->all_dismissed(); + $alert_dismissal_action = YoastSEO()->classes->get( Alert_Dismissal_Action::class ); + $dismissed_alerts = $alert_dismissal_action->all_dismissed(); $script_data = [ 'userLanguageCode' => WPSEO_Language_Utils::get_language( get_user_locale() ), From 926410bd49315b2f156e0de58e03b3dc3b47c0e5 Mon Sep 17 00:00:00 2001 From: Vraja Das <65466507+vraja-pro@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:38:58 +0200 Subject: [PATCH 041/313] Update src/editors/framework/site/post-site-information.php Co-authored-by: Igor <35524806+igorschoester@users.noreply.github.com> --- src/editors/framework/site/post-site-information.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editors/framework/site/post-site-information.php b/src/editors/framework/site/post-site-information.php index bd04e597aa3..5149e7f27bb 100644 --- a/src/editors/framework/site/post-site-information.php +++ b/src/editors/framework/site/post-site-information.php @@ -41,7 +41,7 @@ class Post_Site_Information extends Base_Site_Information { * * @var WooCommerce_Conditional $woocommerce_conditional */ - protected $woocommerce_conditional; + private $woocommerce_conditional; /** * Constructs the class. From d223791cfcdc978494357086b3e32e0563bace71 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 4 Sep 2024 15:48:20 +0300 Subject: [PATCH 042/313] fix tests --- .../Editors/Framework/Site/Post_Site_Information_Test.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php index 53d015981fb..de61c899c8b 100644 --- a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php @@ -126,7 +126,7 @@ public function test_legacy_site_information() { ], 'webinarIntroBlockEditorUrl' => 'https://expl.c', 'blackFridayBlockEditorUrl' => '', - 'isWooCommerceActive' => '', + 'isWooCommerceActive' => false, 'metabox' => [ 'search_url' => 'https://example.org', 'post_edit_url' => 'https://example.org', @@ -155,7 +155,7 @@ public function test_legacy_site_information() { $this->promotion_manager->expects( 'get_current_promotions' )->andReturn( [ 'the promotion', 'another one' ] ); $this->promotion_manager->expects( 'is' )->andReturnFalse(); $this->short_link_helper->expects( 'get' )->andReturn( 'https://expl.c' ); - $this->woocommerce_conditional->expects( 'is_met' )->andReturn( '' ); + $this->woocommerce_conditional->expects( 'is_met' )->andReturn( false ); $this->assertSame( $expected, $this->instance->get_legacy_site_information() ); } @@ -187,7 +187,7 @@ public function test_site_information() { 'search_url' => 'https://example.org', 'post_edit_url' => 'https://example.org', 'base_url' => 'https://example.org', - 'isWooCommerceActive' => '1', + 'isWooCommerceActive' => true, 'adminUrl' => 'https://example.org', 'linkParams' => [ 'param', @@ -211,7 +211,7 @@ public function test_site_information() { $this->promotion_manager->expects( 'get_current_promotions' )->andReturn( [ 'the promotion', 'another one' ] ); $this->promotion_manager->expects( 'is' )->andReturnFalse(); $this->short_link_helper->expects( 'get' )->andReturn( 'https://expl.c' ); - $this->woocommerce_conditional->expects( 'is_met' )->andReturn( '1' ); + $this->woocommerce_conditional->expects( 'is_met' )->andReturn( true ); $this->assertSame( $expected, $this->instance->get_site_information() ); } From bd5fd7ef172f283f6bd037362d07cd65bb8b885a Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Thu, 5 Sep 2024 10:06:50 +0200 Subject: [PATCH 043/313] Transforms some more assessors to use class syntax --- .../cornerstone/relatedKeywordAssessor.js | 65 +++------ .../cornerstone/seoAssessor.js | 102 ++----------- .../collectionPages/relatedKeywordAssessor.js | 76 +++++----- .../assessors/collectionPages/seoAssessor.js | 136 +++++++++--------- .../relatedKeywordTaxonomyAssessor.js | 19 +-- 5 files changed, 139 insertions(+), 259 deletions(-) diff --git a/packages/yoastseo/src/scoring/assessors/collectionPages/cornerstone/relatedKeywordAssessor.js b/packages/yoastseo/src/scoring/assessors/collectionPages/cornerstone/relatedKeywordAssessor.js index 5d2942049d6..c23beca0adc 100644 --- a/packages/yoastseo/src/scoring/assessors/collectionPages/cornerstone/relatedKeywordAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/collectionPages/cornerstone/relatedKeywordAssessor.js @@ -1,53 +1,24 @@ -import { inherits } from "util"; - -import Assessor from "../../assessor.js"; -import IntroductionKeywordAssessment from "../../../assessments/seo/IntroductionKeywordAssessment.js"; -import KeyphraseLengthAssessment from "../../../assessments/seo/KeyphraseLengthAssessment.js"; -import KeyphraseDensityAssessment from "../../../assessments/seo/KeywordDensityAssessment.js"; +import CollectionRelatedKeywordAssessor from "../relatedKeywordAssessor.js"; import MetaDescriptionKeywordAssessment from "../../../assessments/seo/MetaDescriptionKeywordAssessment.js"; -import FunctionWordsInKeyphraseAssessment from "../../../assessments/seo/FunctionWordsInKeyphraseAssessment.js"; import { createAnchorOpeningTag } from "../../../../helpers"; /** - * Creates the Assessor used for collection pages. - * - * @param {Researcher} researcher The researcher to use for the analysis. - * @param {Object?} options The options for this assessor. - * - * @constructor + * The CollectionCornerstoneRelatedKeywordAssessor class is used for the related keyword analysis for cornerstone collections. */ -const CollectionCornerstoneRelatedKeywordAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "collectionCornerstoneRelatedKeywordAssessor"; - - this._assessments = [ - new IntroductionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify8" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify9" ), - } ), - new KeyphraseLengthAssessment( { - isRelatedKeyphrase: true, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), - } ), - new KeyphraseDensityAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify12" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify13" ), - } ), - new MetaDescriptionKeywordAssessment( - { parameters: { recommendedMinimum: 1 }, - scores: { good: 9, bad: 3 }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), - } - ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), - } ), - ]; -}; - -inherits( CollectionCornerstoneRelatedKeywordAssessor, Assessor ); +export default class CollectionCornerstoneRelatedKeywordAssessor extends CollectionRelatedKeywordAssessor { + /** + * Creates a new CollectionCornerstoneRelatedKeywordAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "collectionRelatedKeywordAssessor"; -export default CollectionCornerstoneRelatedKeywordAssessor; + this.addAssessment( "metaDescriptionKeyword", new MetaDescriptionKeywordAssessment( { + parameters: { recommendedMinimum: 1 }, scores: { good: 9, bad: 3 }, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), + } ) ); + } +} diff --git a/packages/yoastseo/src/scoring/assessors/collectionPages/cornerstone/seoAssessor.js b/packages/yoastseo/src/scoring/assessors/collectionPages/cornerstone/seoAssessor.js index 674c7fd97f4..ac4abb9304e 100644 --- a/packages/yoastseo/src/scoring/assessors/collectionPages/cornerstone/seoAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/collectionPages/cornerstone/seoAssessor.js @@ -1,57 +1,21 @@ -import { inherits } from "util"; - -import Assessor from "../../assessor"; -import IntroductionKeywordAssessment from "../../../assessments/seo/IntroductionKeywordAssessment.js"; -import KeyphraseLengthAssessment from "../../../assessments/seo/KeyphraseLengthAssessment.js"; -import KeyphraseDensityAssessment from "../../../assessments/seo/KeywordDensityAssessment.js"; -import MetaDescriptionKeywordAssessment from "../../../assessments/seo/MetaDescriptionKeywordAssessment.js"; -import FunctionWordsInKeyphraseAssessment from "../../../assessments/seo/FunctionWordsInKeyphraseAssessment.js"; -import MetaDescriptionLengthAssessment from "../../../assessments/seo/MetaDescriptionLengthAssessment.js"; +import CollectionSEOAssessor from "../seoAssessor"; import TextLengthAssessment from "../../../assessments/seo/TextLengthAssessment.js"; -import KeyphraseInSEOTitleAssessment from "../../../assessments/seo/KeyphraseInSEOTitleAssessment.js"; -import PageTitleWidthAssessment from "../../../assessments/seo/PageTitleWidthAssessment.js"; -import SlugKeywordAssessment from "../../../assessments/seo/UrlKeywordAssessment.js"; -import SingleH1Assessment from "../../../assessments/seo/SingleH1Assessment.js"; - import { createAnchorOpeningTag } from "../../../../helpers"; /** - * Creates the Assessor used for collection pages. - * - * @param {Researcher} researcher The researcher used for the analysis. - * @param {Object?} options The options for this assessor. - * @constructor + * The CollectionCornerstoneSEOAssessor class is used for the SEO analysis for cornerstone collections. */ -const CollectionCornerstoneSEOAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "collectionCornerstoneSEOAssessor"; +export default class CollectionCornerstoneSEOAssessor extends CollectionSEOAssessor { + /** + * Creates a new CollectionCornerstoneRelatedKeywordAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "collectionCornerstoneSEOAssessor"; - this._assessments = [ - new IntroductionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify8" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify9" ), - } ), - new KeyphraseLengthAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), - } ), - new KeyphraseDensityAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify12" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify13" ), - } ), - new MetaDescriptionKeywordAssessment( - { - parameters: { recommendedMinimum: 1 }, - scores: { good: 9, bad: 3 }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), - } - ), - new MetaDescriptionLengthAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify46" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify47" ), - } ), - new TextLengthAssessment( { + this.addAssessment( "textLength", new TextLengthAssessment( { recommendedMinimum: 30, slightlyBelowMinimum: 10, veryFarBelowMinimum: 1, @@ -59,42 +23,6 @@ const CollectionCornerstoneSEOAssessor = function( researcher, options ) { urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify59" ), cornerstoneContent: true, customContentType: this.type, - } ), - new KeyphraseInSEOTitleAssessment( - { - parameters: { - recommendedPosition: 0, - }, - scores: { - good: 9, - bad: 2, - }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify24" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify25" ), - } - ), - new PageTitleWidthAssessment( { - scores: { - widthTooShort: 9, - }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify52" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify53" ), - }, true ), - new SlugKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify26" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify27" ), - } ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), - } ), - new SingleH1Assessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify54" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify55" ), - } ), - ]; -}; - -inherits( CollectionCornerstoneSEOAssessor, Assessor ); - -export default CollectionCornerstoneSEOAssessor; + } ) ); + } +} diff --git a/packages/yoastseo/src/scoring/assessors/collectionPages/relatedKeywordAssessor.js b/packages/yoastseo/src/scoring/assessors/collectionPages/relatedKeywordAssessor.js index 106fa61673a..136325fd924 100644 --- a/packages/yoastseo/src/scoring/assessors/collectionPages/relatedKeywordAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/collectionPages/relatedKeywordAssessor.js @@ -1,6 +1,4 @@ -import { inherits } from "util"; - -import Assessor from "../assessor"; +import RelatedKeywordAssessor from "../relatedKeywordAssessor"; import IntroductionKeywordAssessment from "../../assessments/seo/IntroductionKeywordAssessment.js"; import KeyphraseLengthAssessment from "../../assessments/seo/KeyphraseLengthAssessment.js"; import KeyphraseDensityAssessment from "../../assessments/seo/KeywordDensityAssessment.js"; @@ -9,42 +7,40 @@ import FunctionWordsInKeyphraseAssessment from "../../assessments/seo/FunctionWo import { createAnchorOpeningTag } from "../../../helpers"; /** - * Creates the Assessor used for collection pages. - * - * @param {Researcher} researcher The researcher to use for the analysis. - * @param {Object?} options The options for this assessor. - * - * @constructor + * The CollectionRelatedKeywordAssessor class is used for the related keyword analysis for collections. */ -const CollectionRelatedKeywordAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "collectionRelatedKeywordAssessor"; - - this._assessments = [ - new IntroductionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify8" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify9" ), - } ), - new KeyphraseLengthAssessment( { - isRelatedKeyphrase: true, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), - } ), - new KeyphraseDensityAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify12" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify13" ), - } ), - new MetaDescriptionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), - } ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), - } ), - ]; -}; - -inherits( CollectionRelatedKeywordAssessor, Assessor ); +export default class CollectionRelatedKeywordAssessor extends RelatedKeywordAssessor { + /** + * Creates a new CollectionRelatedKeywordAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "collectionRelatedKeywordAssessor"; -export default CollectionRelatedKeywordAssessor; + this._assessments = [ + new IntroductionKeywordAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify8" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify9" ), + } ), + new KeyphraseLengthAssessment( { + isRelatedKeyphrase: true, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), + } ), + new KeyphraseDensityAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify12" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify13" ), + } ), + new MetaDescriptionKeywordAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), + } ), + new FunctionWordsInKeyphraseAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), + } ), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/assessors/collectionPages/seoAssessor.js b/packages/yoastseo/src/scoring/assessors/collectionPages/seoAssessor.js index cba89aa451e..0bedf72546a 100644 --- a/packages/yoastseo/src/scoring/assessors/collectionPages/seoAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/collectionPages/seoAssessor.js @@ -1,6 +1,4 @@ -import { inherits } from "util"; - -import Assessor from "../assessor.js"; +import SEOAssessor from "../seoAssessor.js"; import IntroductionKeywordAssessment from "../../assessments/seo/IntroductionKeywordAssessment.js"; import KeyphraseLengthAssessment from "../../assessments/seo/KeyphraseLengthAssessment.js"; import KeyphraseDensityAssessment from "../../assessments/seo/KeywordDensityAssessment.js"; @@ -12,75 +10,73 @@ import KeyphraseInSEOTitleAssessment from "../../assessments/seo/KeyphraseInSEOT import PageTitleWidthAssessment from "../../assessments/seo/PageTitleWidthAssessment.js"; import SlugKeywordAssessment from "../../assessments/seo/UrlKeywordAssessment.js"; import SingleH1Assessment from "../../assessments/seo/SingleH1Assessment.js"; - import { createAnchorOpeningTag } from "../../../helpers"; /** - * Creates the Assessor used for collection pages. - * - * @param {Researcher} researcher The researcher used for the analysis. - * @param {Object?} options The options for this assessor. - * @constructor + * The CollectionSEOAssessor class is used for the SEO analysis for collections. */ -const CollectionSEOAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "collectionSEOAssessor"; - - this._assessments = [ - new IntroductionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify8" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify9" ), - } ), - new KeyphraseLengthAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), - } ), - new KeyphraseDensityAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify12" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify13" ), - } ), - new MetaDescriptionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), - } ), - new MetaDescriptionLengthAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify46" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify47" ), - } ), - new TextLengthAssessment( { - recommendedMinimum: 30, - slightlyBelowMinimum: 10, - veryFarBelowMinimum: 1, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify58" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify59" ), - customContentType: this.type, - } ), - new KeyphraseInSEOTitleAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify24" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify25" ), - } ), - new PageTitleWidthAssessment( { - scores: { - widthTooShort: 9, - }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify52" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify53" ), - }, true ), - new SlugKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify26" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify27" ), - } ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), - } ), - new SingleH1Assessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify54" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify55" ), - } ), - ]; -}; - -inherits( CollectionSEOAssessor, Assessor ); +export default class CollectionSEOAssessor extends SEOAssessor { + /** + * Creates a new CollectionSEOAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "collectionSEOAssessor"; -export default CollectionSEOAssessor; + this._assessments = [ + new IntroductionKeywordAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify8" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify9" ), + } ), + new KeyphraseLengthAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), + } ), + new KeyphraseDensityAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify12" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify13" ), + } ), + new MetaDescriptionKeywordAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), + } ), + new MetaDescriptionLengthAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify46" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify47" ), + } ), + new TextLengthAssessment( { + recommendedMinimum: 30, + slightlyBelowMinimum: 10, + veryFarBelowMinimum: 1, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify58" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify59" ), + customContentType: this.type, + } ), + new KeyphraseInSEOTitleAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify24" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify25" ), + } ), + new PageTitleWidthAssessment( { + scores: { + widthTooShort: 9, + }, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify52" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify53" ), + }, true ), + new SlugKeywordAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify26" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify27" ), + } ), + new FunctionWordsInKeyphraseAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), + } ), + new SingleH1Assessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify54" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify55" ), + } ), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/assessors/relatedKeywordTaxonomyAssessor.js b/packages/yoastseo/src/scoring/assessors/relatedKeywordTaxonomyAssessor.js index dce990da0a0..5356129466e 100644 --- a/packages/yoastseo/src/scoring/assessors/relatedKeywordTaxonomyAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/relatedKeywordTaxonomyAssessor.js @@ -1,14 +1,9 @@ -import Assessor from "./assessor.js"; -import IntroductionKeywordAssessment from "../assessments/seo/IntroductionKeywordAssessment.js"; -import KeyphraseLengthAssessment from "../assessments/seo/KeyphraseLengthAssessment.js"; -import KeyphraseDensityAssessment from "../assessments/seo/KeywordDensityAssessment.js"; -import MetaDescriptionKeywordAssessment from "../assessments/seo/MetaDescriptionKeywordAssessment.js"; -import FunctionWordsInKeyphrase from "../assessments/seo/FunctionWordsInKeyphraseAssessment.js"; +import RelatedKeywordAssessor from "./relatedKeywordAssessor.js"; /** * The RelatedKeywordTaxonomyAssessor class is used for the related keyword analysis on terms. */ -export default class RelatedKeywordTaxonomyAssessor extends Assessor { +export default class RelatedKeywordTaxonomyAssessor extends RelatedKeywordAssessor { /** * Creates a new RelatedKeywordTaxonomyAssessor instance. * @param {Researcher} researcher The researcher to use. @@ -18,13 +13,7 @@ export default class RelatedKeywordTaxonomyAssessor extends Assessor { super( researcher, options ); this.type = "relatedKeywordsTaxonomyAssessor"; - this._assessments = [ - new IntroductionKeywordAssessment(), - new KeyphraseLengthAssessment( { isRelatedKeyphrase: true } ), - new KeyphraseDensityAssessment(), - new MetaDescriptionKeywordAssessment(), - // Text Images assessment here. - new FunctionWordsInKeyphrase(), - ]; + this.removeAssessment( "textCompetingLinks" ); + this.removeAssessment( "imageKeyphrase" ); } } From 879f924971914a0b63282f8bf06fbbea870e1634 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 5 Sep 2024 11:11:30 +0300 Subject: [PATCH 044/313] remove from post site information layer --- .../framework/site/post-site-information.php | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/editors/framework/site/post-site-information.php b/src/editors/framework/site/post-site-information.php index 5149e7f27bb..cfb6b94d65a 100644 --- a/src/editors/framework/site/post-site-information.php +++ b/src/editors/framework/site/post-site-information.php @@ -3,7 +3,6 @@ namespace Yoast\WP\SEO\Editors\Framework\Site; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; -use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; @@ -36,13 +35,6 @@ class Post_Site_Information extends Base_Site_Information { */ private $promotion_manager; - /** - * The WooCommerce conditional. - * - * @var WooCommerce_Conditional $woocommerce_conditional - */ - private $woocommerce_conditional; - /** * Constructs the class. * @@ -53,7 +45,6 @@ class Post_Site_Information extends Base_Site_Information { * @param Meta_Surface $meta The meta surface. * @param Product_Helper $product_helper The product helper. * @param Alert_Dismissal_Action $alert_dismissal_action The alert dismissal action. - * @param WooCommerce_Conditional $woocommerce_conditional The WooCommerce conditional. * * @return void */ @@ -63,13 +54,11 @@ public function __construct( Wistia_Embed_Permission_Repository $wistia_embed_permission_repository, Meta_Surface $meta, Product_Helper $product_helper, - Alert_Dismissal_Action $alert_dismissal_action, - WooCommerce_Conditional $woocommerce_conditional + Alert_Dismissal_Action $alert_dismissal_action ) { parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper ); $this->promotion_manager = $promotion_manager; $this->alert_dismissal_action = $alert_dismissal_action; - $this->woocommerce_conditional = $woocommerce_conditional; } /** @@ -96,7 +85,6 @@ public function get_legacy_site_information(): array { 'currentPromotions' => $this->promotion_manager->get_current_promotions(), 'webinarIntroBlockEditorUrl' => $this->short_link_helper->get( 'https://yoa.st/webinar-intro-block-editor' ), 'blackFridayBlockEditorUrl' => ( $this->promotion_manager->is( 'black-friday-2023-checklist' ) ) ? $this->short_link_helper->get( 'https://yoa.st/black-friday-checklist' ) : '', - 'isWooCommerceActive' => $this->woocommerce_conditional->is_met(), 'metabox' => [ 'search_url' => $this->search_url(), 'post_edit_url' => $this->edit_url(), @@ -123,7 +111,6 @@ public function get_site_information(): array { 'search_url' => $this->search_url(), 'post_edit_url' => $this->edit_url(), 'base_url' => $this->base_url_for_js(), - 'isWooCommerceActive' => $this->woocommerce_conditional->is_met(), ]; return \array_merge( $data, parent::get_site_information() ); From 4848faa736295e2d4122782e1691e331d2a4b515 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 5 Sep 2024 11:11:54 +0300 Subject: [PATCH 045/313] create an integration --- .../framework/integrations/woocommerce.php | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/editors/framework/integrations/woocommerce.php diff --git a/src/editors/framework/integrations/woocommerce.php b/src/editors/framework/integrations/woocommerce.php new file mode 100644 index 00000000000..2258a4d5ff0 --- /dev/null +++ b/src/editors/framework/integrations/woocommerce.php @@ -0,0 +1,55 @@ +woocommerce_conditional = $woocommerce_conditional; + } + + /** + * If the plugin is activated. + * + * @return bool If the plugin is activated. + */ + public function is_enabled(): bool { + return $this->woocommerce_conditional->is_met(); + } + + /** + * Return this object represented by a key value array. + * + * @return array Returns the name and if the feature is enabled. + */ + public function to_array(): array { + return [ 'isWooCommerceActive' => $this->is_enabled() ]; + } + + /** + * Returns this object represented by a key value structure that is compliant with the script data array. + * + * @return array Returns the legacy key and if the feature is enabled. + */ + public function to_legacy_array(): array { + return [ 'isWooCommerceActive' => $this->is_enabled() ]; + } +} From d102f16448cb906647fceda291211263827e559b Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 5 Sep 2024 11:12:30 +0300 Subject: [PATCH 046/313] match the name of multilingual plugin check --- src/editors/framework/integrations/multilingual.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editors/framework/integrations/multilingual.php b/src/editors/framework/integrations/multilingual.php index 4f643dbca4a..1d78a5dd66a 100644 --- a/src/editors/framework/integrations/multilingual.php +++ b/src/editors/framework/integrations/multilingual.php @@ -61,7 +61,7 @@ public function is_enabled(): bool { * @return array Returns the name and if the feature is enabled. */ public function to_array(): array { - return [ 'multilingualPluginActive' => $this->is_enabled() ]; + return [ 'isMultilingualActive' => $this->is_enabled() ]; } /** From f83d3d2c558a98ed96b7164b1d4f0374b4694af4 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 5 Sep 2024 11:12:47 +0300 Subject: [PATCH 047/313] change the helper to match --- packages/js/src/helpers/isWooCommerceActive.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/js/src/helpers/isWooCommerceActive.js b/packages/js/src/helpers/isWooCommerceActive.js index 26cf5318076..f7a6afcccc1 100644 --- a/packages/js/src/helpers/isWooCommerceActive.js +++ b/packages/js/src/helpers/isWooCommerceActive.js @@ -1,9 +1,11 @@ +import { get } from "lodash"; + /** * Checks if WooCommerce is active. * * @returns {boolean} True if WooCommerce is active. */ export const isWooCommerceActive = () => { - return window.wpseoScriptData && window.wpseoScriptData.isWooCommerceActive === "1"; + return get( window, "wpseoScriptData.metabox.isWooCommerceActive", false ); }; From 7bb6dd3b9ecfdef6e22d862a253db67016b03e51 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 5 Sep 2024 11:13:07 +0300 Subject: [PATCH 048/313] update the selector --- packages/js/src/redux/selectors/isWooSEO.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/js/src/redux/selectors/isWooSEO.js b/packages/js/src/redux/selectors/isWooSEO.js index 531e12ea98f..01d847c129e 100644 --- a/packages/js/src/redux/selectors/isWooSEO.js +++ b/packages/js/src/redux/selectors/isWooSEO.js @@ -1,5 +1,6 @@ import { get } from "lodash"; import { getIsProduct } from "./editorContext"; +import { isWooCommerceActive } from "../../helpers/isWooCommerceActive"; /** * Determines whether the WooCommerce SEO addon is active. @@ -13,7 +14,7 @@ export const getIsWooSeoActive = () => Boolean( get( window, "wpseoScriptData.is * * @returns {boolean} True if WooCommerce is active. */ -export const getIsWooCommerceActive = () => Boolean( get( window, "wpseoScriptData.isWooCommerceActive", false ) ); +export const getIsWooCommerceActive = () => isWooCommerceActive(); /** * Determines whether the WooCommerce SEO addon is not active in a product page. From 4b7a72112bcd08b71c4924cc1e7198811d36a624 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 5 Sep 2024 11:14:32 +0300 Subject: [PATCH 049/313] Revert "fix tests" This reverts commit d7258611e64e8c8bc13f6ee0f3c3eb13f5155401. --- .../Site/Post_Site_Information_Test.php | 31 ++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php index de61c899c8b..c3ecc881b8f 100644 --- a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php @@ -6,7 +6,6 @@ use Brain\Monkey; use Mockery; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; -use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Editors\Framework\Site\Post_Site_Information; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; @@ -69,13 +68,6 @@ final class Post_Site_Information_Test extends TestCase { */ private $product_helper; - /** - * The WooCommerce conditional. - * - * @var WooCommerce_Conditional $woocommerce_conditional - */ - protected $woocommerce_conditional; - /** * The Post_Site_Information container. * @@ -90,15 +82,14 @@ final class Post_Site_Information_Test extends TestCase { */ protected function set_up() { parent::set_up(); - $this->promotion_manager = Mockery::mock( Promotion_Manager::class ); - $this->short_link_helper = Mockery::mock( Short_Link_Helper::class ); - $this->wistia_embed_repo = Mockery::mock( Wistia_Embed_Permission_Repository::class ); - $this->meta_surface = Mockery::mock( Meta_Surface::class ); - $this->product_helper = Mockery::mock( Product_Helper::class ); - $this->alert_dismissal_action = Mockery::mock( Alert_Dismissal_Action::class ); - $this->woocommerce_conditional = Mockery::mock( WooCommerce_Conditional::class ); - - $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action, $this->woocommerce_conditional ); + $this->promotion_manager = Mockery::mock( Promotion_Manager::class ); + $this->short_link_helper = Mockery::mock( Short_Link_Helper::class ); + $this->wistia_embed_repo = Mockery::mock( Wistia_Embed_Permission_Repository::class ); + $this->meta_surface = Mockery::mock( Meta_Surface::class ); + $this->product_helper = Mockery::mock( Product_Helper::class ); + $this->alert_dismissal_action = Mockery::mock( Alert_Dismissal_Action::class ); + + $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action ); $this->instance->set_permalink( 'perma' ); $this->set_mocks(); } @@ -126,7 +117,6 @@ public function test_legacy_site_information() { ], 'webinarIntroBlockEditorUrl' => 'https://expl.c', 'blackFridayBlockEditorUrl' => '', - 'isWooCommerceActive' => false, 'metabox' => [ 'search_url' => 'https://example.org', 'post_edit_url' => 'https://example.org', @@ -146,6 +136,7 @@ public function test_legacy_site_information() { ], 'pluginUrl' => '/location', 'wistiaEmbedPermission' => true, + ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); @@ -155,7 +146,6 @@ public function test_legacy_site_information() { $this->promotion_manager->expects( 'get_current_promotions' )->andReturn( [ 'the promotion', 'another one' ] ); $this->promotion_manager->expects( 'is' )->andReturnFalse(); $this->short_link_helper->expects( 'get' )->andReturn( 'https://expl.c' ); - $this->woocommerce_conditional->expects( 'is_met' )->andReturn( false ); $this->assertSame( $expected, $this->instance->get_legacy_site_information() ); } @@ -187,7 +177,7 @@ public function test_site_information() { 'search_url' => 'https://example.org', 'post_edit_url' => 'https://example.org', 'base_url' => 'https://example.org', - 'isWooCommerceActive' => true, + 'adminUrl' => 'https://example.org', 'linkParams' => [ 'param', @@ -211,7 +201,6 @@ public function test_site_information() { $this->promotion_manager->expects( 'get_current_promotions' )->andReturn( [ 'the promotion', 'another one' ] ); $this->promotion_manager->expects( 'is' )->andReturnFalse(); $this->short_link_helper->expects( 'get' )->andReturn( 'https://expl.c' ); - $this->woocommerce_conditional->expects( 'is_met' )->andReturn( true ); $this->assertSame( $expected, $this->instance->get_site_information() ); } From 5005e035a9dfae2fb64ce330c9ef2d20f22cc51e Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 5 Sep 2024 11:22:10 +0300 Subject: [PATCH 050/313] restore post-site0information and tests --- .../framework/site/post-site-information.php | 4 ++-- .../Site/Post_Site_Information_Test.php | 19 ++++--------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/editors/framework/site/post-site-information.php b/src/editors/framework/site/post-site-information.php index cfb6b94d65a..4882642aa7a 100644 --- a/src/editors/framework/site/post-site-information.php +++ b/src/editors/framework/site/post-site-information.php @@ -57,8 +57,8 @@ public function __construct( Alert_Dismissal_Action $alert_dismissal_action ) { parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper ); - $this->promotion_manager = $promotion_manager; - $this->alert_dismissal_action = $alert_dismissal_action; + $this->promotion_manager = $promotion_manager; + $this->alert_dismissal_action = $alert_dismissal_action; } /** diff --git a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php index c96860eaba1..a7034a1c5c5 100644 --- a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php @@ -5,7 +5,6 @@ use Mockery; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; -use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Editors\Framework\Site\Post_Site_Information; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; @@ -65,13 +64,6 @@ final class Post_Site_Information_Test extends TestCase { */ private $product_helper; - /** - * The WooCommerce conditional. - * - * @var WooCommerce_Conditional $woocommerce_conditional - */ - protected $woocommerce_conditional; - /** * The Post_Site_Information container. * @@ -90,12 +82,11 @@ public function set_up() { $this->short_link_helper = \YoastSEO()->helpers->short_link; $this->wistia_embed_repo = Mockery::mock( Wistia_Embed_Permission_Repository::class ); $this->wistia_embed_repo->expects( 'get_value_for_user' )->with( 0 )->andReturnTrue(); - $this->meta_surface = \YoastSEO()->meta; - $this->product_helper = \YoastSEO()->helpers->product; - $this->alert_dismissal_action = \YoastSEO()->classes->get( Alert_Dismissal_Action::class ); - $this->woocommerce_conditional = \YoastSEO()->classes->get( WooCommerce_Conditional::class ); + $this->meta_surface = \YoastSEO()->meta; + $this->product_helper = \YoastSEO()->helpers->product; + $this->alert_dismissal_action = \YoastSEO()->classes->get( Alert_Dismissal_Action::class ); - $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action, $this->woocommerce_conditional ); + $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action ); $this->instance->set_permalink( 'perma' ); } @@ -117,7 +108,6 @@ public function test_legacy_site_information() { 'currentPromotions' => [], 'webinarIntroBlockEditorUrl' => $this->short_link_helper->get( 'https://yoa.st/webinar-intro-block-editor' ), 'blackFridayBlockEditorUrl' => '', - 'isWooCommerceActive' => false, 'metabox' => [ 'search_url' => 'http://example.org/wp-admin/edit.php?seo_kw_filter={keyword}', @@ -160,7 +150,6 @@ public function test_site_information() { 'search_url' => 'http://example.org/wp-admin/edit.php?seo_kw_filter={keyword}', 'post_edit_url' => 'http://example.org/wp-admin/post.php?post={id}&action=edit', 'base_url' => 'http://example.org/', - 'isWooCommerceActive' => false, 'adminUrl' => 'http://example.org/wp-admin/admin.php', 'linkParams' => $this->short_link_helper->get_query_params(), 'pluginUrl' => 'http://example.org/wp-content/plugins/wordpress-seo', From 10a62bd173f3ea0eeedf292da155400c2ee01efe Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 5 Sep 2024 11:35:27 +0300 Subject: [PATCH 051/313] Add tests to new integration --- .../Integrations/WooCommerce_Test.php | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tests/Unit/Editors/Framework/Integrations/WooCommerce_Test.php diff --git a/tests/Unit/Editors/Framework/Integrations/WooCommerce_Test.php b/tests/Unit/Editors/Framework/Integrations/WooCommerce_Test.php new file mode 100644 index 00000000000..c1e7647a105 --- /dev/null +++ b/tests/Unit/Editors/Framework/Integrations/WooCommerce_Test.php @@ -0,0 +1,85 @@ +woocommerce_conditional = Mockery::mock( WooCommerce_Conditional::class ); + $this->instance = new WooCommerce( $this->woocommerce_conditional ); + } + + /** + * Tests the is_enabled method. + * + * @dataProvider data_provider_is_enabled + * + * @param bool $woocommerce_enabled If the woocommerce plugin is enabled. + * @param bool $expected The expected outcome. + * + * @return void + */ + public function test_is_enabled( + bool $woocommerce_enabled, + bool $expected + ) { + + $this->woocommerce_conditional + ->expects( 'is_met' ) + ->times( 3 ) + ->andReturn( $woocommerce_enabled ); + + $this->assertSame( $expected, $this->instance->is_enabled() ); + $this->assertSame( [ 'isWooCommerceActive' => $this->instance->is_enabled() ], $this->instance->to_legacy_array() ); + } + + /** + * Data provider for test_is_enabled. + * + * @return array> + */ + public static function data_provider_is_enabled() { + return [ + 'Enabled' => [ + 'woocommerce_enabled' => true, + 'expected' => true, + ], + 'Disabled' => [ + 'woocommerce_enabled' => false, + 'expected' => false, + ], + ]; + } +} From d02fc4f4fa568d0c63297c8cb4933ff0160d7a90 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 5 Sep 2024 11:40:30 +0300 Subject: [PATCH 052/313] Add comment why we have this selector. --- packages/js/src/redux/selectors/isWooSEO.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/src/redux/selectors/isWooSEO.js b/packages/js/src/redux/selectors/isWooSEO.js index 01d847c129e..c30ae0a483f 100644 --- a/packages/js/src/redux/selectors/isWooSEO.js +++ b/packages/js/src/redux/selectors/isWooSEO.js @@ -10,7 +10,7 @@ import { isWooCommerceActive } from "../../helpers/isWooCommerceActive"; export const getIsWooSeoActive = () => Boolean( get( window, "wpseoScriptData.isWooCommerceSeoActive", false ) ); /** - * Checks if WooCommerce is active. + * Checks if WooCommerce is active. Used also in premium. * * @returns {boolean} True if WooCommerce is active. */ From 1ee93dbe8ad27e5f6e021dccf5bb3f3e1e3cf5cf Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 5 Sep 2024 11:52:22 +0300 Subject: [PATCH 053/313] fix lint warning --- packages/js/src/redux/selectors/isWooSEO.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js/src/redux/selectors/isWooSEO.js b/packages/js/src/redux/selectors/isWooSEO.js index c30ae0a483f..65c16f90b6a 100644 --- a/packages/js/src/redux/selectors/isWooSEO.js +++ b/packages/js/src/redux/selectors/isWooSEO.js @@ -24,8 +24,8 @@ export const getIsWooCommerceActive = () => isWooCommerceActive(); */ export const getIsWooSeoUpsell = ( state ) => { const isWooSeoActive = getIsWooSeoActive(); - const isWooCommerceActive = getIsWooCommerceActive(); + const isWoocommerceActive = getIsWooCommerceActive(); const isProductPage = getIsProduct( state ); - return ! isWooSeoActive && isWooCommerceActive && isProductPage; + return ! isWooSeoActive && isWoocommerceActive && isProductPage; }; From ba5e26435f00f4c53ef41b2ee577821dad7241af Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Thu, 5 Sep 2024 14:58:37 +0200 Subject: [PATCH 054/313] Fixes a test for App --- packages/yoastseo/spec/appSpec.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/yoastseo/spec/appSpec.js b/packages/yoastseo/spec/appSpec.js index ea9f8f4aad8..1e437ddc451 100644 --- a/packages/yoastseo/spec/appSpec.js +++ b/packages/yoastseo/spec/appSpec.js @@ -8,7 +8,7 @@ App.prototype.removeLoadingDialog = function() {}; App.prototype.runAnalyzer = function() {}; // Makes lodash think this is a valid HTML element -var mockElement = []; +const mockElement = []; mockElement.nodeType = 1; global.document = {}; @@ -18,13 +18,11 @@ document.getElementById = function() { describe( "Creating an App", function() { it( "throws an error when no args are given", function() { - expect( App ).toThrowError( MissingArgument ); + expect( () => new App() ).toThrowError( MissingArgument ); } ); it( "throws on an empty args object", function() { - expect( function() { - new App( {} ); - } ).toThrowError( MissingArgument ); + expect( () => new App( {} ) ).toThrowError( MissingArgument ); } ); it( "throws on an invalid targets argument", function() { From 44a426b1d5d052890ba2b302a0adc6b5a42edcac Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Thu, 5 Sep 2024 14:58:56 +0200 Subject: [PATCH 055/313] Disables complexity check for isValid in Mark --- packages/yoastseo/src/values/Mark.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/yoastseo/src/values/Mark.js b/packages/yoastseo/src/values/Mark.js index e07eece97d2..89564171df9 100644 --- a/packages/yoastseo/src/values/Mark.js +++ b/packages/yoastseo/src/values/Mark.js @@ -213,6 +213,7 @@ class Mark { }; } + /* eslint-disable complexity */ /** * Checks if the mark object is valid for position-based highlighting. * @returns {void} @@ -233,6 +234,7 @@ class Mark { throw new Error( "A mark object should either have start and end defined or start and end undefined." ); } } + /* eslint-enable complexity */ /** * Checks if a mark has position information available. From c3c981a05cd402a63bc75d837b46aaa5d14a4c4a Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Thu, 5 Sep 2024 15:00:34 +0200 Subject: [PATCH 056/313] Transforms the assessors to use class syntax --- .../cornerstone/contentAssessorSpec.js | 8 +- .../cornerstone/seoAssessor.js | 2 +- .../assessors/productPages/contentAssessor.js | 108 ++++----- .../cornerstone/contentAssessor.js | 84 ++----- .../cornerstone/relatedKeywordAssessor.js | 80 ++---- .../productPages/cornerstone/seoAssessor.js | 166 +++---------- .../productPages/relatedKeywordAssessor.js | 105 ++++---- .../assessors/productPages/seoAssessor.js | 227 +++++++++--------- .../storeBlog/cornerstone/seoAssessor.js | 85 ++----- .../assessors/storeBlog/seoAssessor.js | 98 ++++---- .../storePostsAndPages/contentAssessor.js | 90 ++++--- .../cornerstone/contentAssessor.js | 79 ++---- .../cornerstone/relatedKeywordAssessor.js | 74 ++---- .../cornerstone/seoAssessor.js | 155 +++--------- .../relatedKeywordAssessor.js | 93 ++++--- .../storePostsAndPages/seoAssessor.js | 178 +++++++------- 16 files changed, 581 insertions(+), 1051 deletions(-) diff --git a/packages/yoastseo/spec/scoring/assessors/storePostsAndPages/cornerstone/contentAssessorSpec.js b/packages/yoastseo/spec/scoring/assessors/storePostsAndPages/cornerstone/contentAssessorSpec.js index 71a089a1937..16d708b86cc 100644 --- a/packages/yoastseo/spec/scoring/assessors/storePostsAndPages/cornerstone/contentAssessorSpec.js +++ b/packages/yoastseo/spec/scoring/assessors/storePostsAndPages/cornerstone/contentAssessorSpec.js @@ -185,13 +185,13 @@ describe( "A test for content assessor for English", function() { expect( assessments.length ).toBe( expected ); expect( assessments.map( ( { identifier } ) => identifier ) ).toEqual( [ - "subheadingsTooLong", "textParagraphTooLong", - "textSentenceLength", "textTransitionWords", "passiveVoice", "textPresence", "sentenceBeginnings", + "subheadingsTooLong", + "textSentenceLength", "wordComplexity", ] ); @@ -323,10 +323,10 @@ describe( "calculateOverallScore for non-English that uses Default researcher", expect( assessments.length ).toBe( expected ); expect( assessments.map( ( { identifier } ) => identifier ) ).toEqual( [ - "subheadingsTooLong", "textParagraphTooLong", - "textSentenceLength", "textPresence", + "subheadingsTooLong", + "textSentenceLength", ] ); } ); diff --git a/packages/yoastseo/src/scoring/assessors/collectionPages/cornerstone/seoAssessor.js b/packages/yoastseo/src/scoring/assessors/collectionPages/cornerstone/seoAssessor.js index ac4abb9304e..e613306ed56 100644 --- a/packages/yoastseo/src/scoring/assessors/collectionPages/cornerstone/seoAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/collectionPages/cornerstone/seoAssessor.js @@ -7,7 +7,7 @@ import { createAnchorOpeningTag } from "../../../../helpers"; */ export default class CollectionCornerstoneSEOAssessor extends CollectionSEOAssessor { /** - * Creates a new CollectionCornerstoneRelatedKeywordAssessor instance. + * Creates a new CollectionCornerstoneSEOAssessor instance. * @param {Researcher} researcher The researcher to use. * @param {Object} [options] The assessor options. */ diff --git a/packages/yoastseo/src/scoring/assessors/productPages/contentAssessor.js b/packages/yoastseo/src/scoring/assessors/productPages/contentAssessor.js index 3d5ed9f56a8..3804c9e8fe4 100644 --- a/packages/yoastseo/src/scoring/assessors/productPages/contentAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/productPages/contentAssessor.js @@ -1,6 +1,3 @@ -import { inherits } from "util"; - -import Assessor from "../assessor.js"; import ContentAssessor from "../contentAssessor.js"; import SubheadingDistributionTooLongAssessment from "../../assessments/readability/SubheadingDistributionTooLongAssessment.js"; import ParagraphTooLongAssessment from "../../assessments/readability/ParagraphTooLongAssessment.js"; @@ -9,62 +6,57 @@ import TransitionWordsAssessment from "../../assessments/readability/TransitionW import PassiveVoiceAssessment from "../../assessments/readability/PassiveVoiceAssessment.js"; import TextPresenceAssessment from "../../assessments/readability/TextPresenceAssessment.js"; import ListAssessment from "../../assessments/readability/ListAssessment.js"; - import { createAnchorOpeningTag } from "../../../helpers"; /** - * Creates the Assessor - * - * @param {object} researcher The researcher to use for the analysis. - * @param {Object} options The options for this assessor. - * - * @constructor + * The ProductContentAssessor class is used for the readability analysis for products. */ -const ProductContentAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "productContentAssessor"; - - this._assessments = [ - new SubheadingDistributionTooLongAssessment( { - shouldNotAppearInShortText: true, - urlTitle: createAnchorOpeningTag( options.subheadingUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.subheadingCTAUrl ), - } ), - new ParagraphTooLongAssessment( { - parameters: { - recommendedLength: 70, - maximumRecommendedLength: 100, - }, - urlTitle: createAnchorOpeningTag( options.paragraphUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.paragraphCTAUrl ), - }, true ), - new SentenceLengthInTextAssessment( { - slightlyTooMany: 20, - farTooMany: 25, - urlTitle: createAnchorOpeningTag( options.sentenceLengthUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.sentenceLengthCTAUrl ), - }, false, true ), - new TransitionWordsAssessment( { - urlTitle: createAnchorOpeningTag( options.transitionWordsUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.transitionWordsCTAUrl ), - } ), - new PassiveVoiceAssessment( { - urlTitle: createAnchorOpeningTag( options.passiveVoiceUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.passiveVoiceCTAUrl ), - } ), - new TextPresenceAssessment( { - urlTitle: createAnchorOpeningTag( options.textPresenceUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.textPresenceCTAUrl ), - } ), - new ListAssessment( { - urlTitle: createAnchorOpeningTag( options.listsUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.listsCTAUrl ), - } ), - ]; -}; - -inherits( ProductContentAssessor, ContentAssessor ); - - -export default ProductContentAssessor; - +export default class ProductContentAssessor extends ContentAssessor { + /** + * Creates a new ProductContentAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "productContentAssessor"; + + this._assessments = [ + new SubheadingDistributionTooLongAssessment( { + shouldNotAppearInShortText: true, + urlTitle: createAnchorOpeningTag( options.subheadingUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.subheadingCTAUrl ), + } ), + new ParagraphTooLongAssessment( { + parameters: { + recommendedLength: 70, + maximumRecommendedLength: 100, + }, + urlTitle: createAnchorOpeningTag( options.paragraphUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.paragraphCTAUrl ), + }, true ), + new SentenceLengthInTextAssessment( { + slightlyTooMany: 20, + farTooMany: 25, + urlTitle: createAnchorOpeningTag( options.sentenceLengthUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.sentenceLengthCTAUrl ), + }, false, true ), + new TransitionWordsAssessment( { + urlTitle: createAnchorOpeningTag( options.transitionWordsUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.transitionWordsCTAUrl ), + } ), + new PassiveVoiceAssessment( { + urlTitle: createAnchorOpeningTag( options.passiveVoiceUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.passiveVoiceCTAUrl ), + } ), + new TextPresenceAssessment( { + urlTitle: createAnchorOpeningTag( options.textPresenceUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.textPresenceCTAUrl ), + } ), + new ListAssessment( { + urlTitle: createAnchorOpeningTag( options.listsUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.listsCTAUrl ), + } ), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/assessors/productPages/cornerstone/contentAssessor.js b/packages/yoastseo/src/scoring/assessors/productPages/cornerstone/contentAssessor.js index cdd4d82aece..ce1c580db02 100644 --- a/packages/yoastseo/src/scoring/assessors/productPages/cornerstone/contentAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/productPages/cornerstone/contentAssessor.js @@ -1,77 +1,33 @@ -import { inherits } from "util"; - -import Assessor from "../../assessor.js"; -import ContentAssessor from "../../contentAssessor.js"; +import ProductContentAssessor from "../contentAssessor.js"; import SubheadingDistributionTooLongAssessment from "../../../assessments/readability/SubheadingDistributionTooLongAssessment.js"; -import ParagraphTooLongAssessment from "../../../assessments/readability/ParagraphTooLongAssessment.js"; import SentenceLengthInTextAssessment from "../../../assessments/readability/SentenceLengthInTextAssessment.js"; -import TransitionWordsAssessment from "../../../assessments/readability/TransitionWordsAssessment.js"; -import PassiveVoiceAssessment from "../../../assessments/readability/PassiveVoiceAssessment.js"; -import TextPresenceAssessment from "../../../assessments/readability/TextPresenceAssessment.js"; -import ListAssessment from "../../../assessments/readability/ListAssessment.js"; - import { createAnchorOpeningTag } from "../../../../helpers"; /** - * Creates the Assessor - * - * @param {object} researcher The researcher to use for the analysis. - * @param {Object} options The options for this assessor. - * - * @constructor + * The ProductContentAssessor class is used for the readability analysis for cornerstone products. */ -const ProductCornerstoneContentAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "productCornerstoneContentAssessor"; - - this._assessments = [ - new SubheadingDistributionTooLongAssessment( { - parameters: { - slightlyTooMany: 250, - farTooMany: 300, - recommendedMaximumLength: 250, - }, +export default class ProductCornerstoneContentAssessor extends ProductContentAssessor { + /** + * Creates a new ProductContentAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "productCornerstoneContentAssessor"; + + this.addAssessment( "subheadingsTooLong", new SubheadingDistributionTooLongAssessment( { + parameters: { slightlyTooMany: 250, farTooMany: 300, recommendedMaximumLength: 250 }, applicableIfTextLongerThan: 250, shouldNotAppearInShortText: true, urlTitle: createAnchorOpeningTag( options.subheadingUrlTitle ), urlCallToAction: createAnchorOpeningTag( options.subheadingCTAUrl ), cornerstoneContent: true, - } ), - new ParagraphTooLongAssessment( { - parameters: { - recommendedLength: 70, - maximumRecommendedLength: 100, - }, - urlTitle: createAnchorOpeningTag( options.paragraphUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.paragraphCTAUrl ), - }, true ), - new SentenceLengthInTextAssessment( { - slightlyTooMany: 15, - farTooMany: 20, + } ) ); + this.addAssessment( "textSentenceLength", new SentenceLengthInTextAssessment( { + slightlyTooMany: 15, farTooMany: 20, urlTitle: createAnchorOpeningTag( options.sentenceLengthUrlTitle ), urlCallToAction: createAnchorOpeningTag( options.sentenceLengthCTAUrl ), - }, true, true ), - new TransitionWordsAssessment( { - urlTitle: createAnchorOpeningTag( options.transitionWordsUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.transitionWordsCTAUrl ), - } ), - new PassiveVoiceAssessment( { - urlTitle: createAnchorOpeningTag( options.passiveVoiceUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.passiveVoiceCTAUrl ), - } ), - new TextPresenceAssessment( { - urlTitle: createAnchorOpeningTag( options.textPresenceUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.textPresenceCTAUrl ), - } ), - new ListAssessment( { - urlTitle: createAnchorOpeningTag( options.listsUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.listsCTAUrl ), - } ), - ]; -}; - -inherits( ProductCornerstoneContentAssessor, ContentAssessor ); - - -export default ProductCornerstoneContentAssessor; - + }, true, true ) ); + } +} diff --git a/packages/yoastseo/src/scoring/assessors/productPages/cornerstone/relatedKeywordAssessor.js b/packages/yoastseo/src/scoring/assessors/productPages/cornerstone/relatedKeywordAssessor.js index 156f336939f..41115a88431 100644 --- a/packages/yoastseo/src/scoring/assessors/productPages/cornerstone/relatedKeywordAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/productPages/cornerstone/relatedKeywordAssessor.js @@ -1,72 +1,24 @@ -import { inherits } from "util"; - -import Assessor from "../../assessor.js"; -import IntroductionKeywordAssessment from "../../../assessments/seo/IntroductionKeywordAssessment.js"; -import KeyphraseLengthAssessment from "../../../assessments/seo/KeyphraseLengthAssessment.js"; -import KeyphraseDensityAssessment from "../../../assessments/seo/KeywordDensityAssessment.js"; -import MetaDescriptionKeywordAssessment from "../../../assessments/seo/MetaDescriptionKeywordAssessment.js"; -import TextCompetingLinksAssessment from "../../../assessments/seo/TextCompetingLinksAssessment.js"; -import FunctionWordsInKeyphraseAssessment from "../../../assessments/seo/FunctionWordsInKeyphraseAssessment.js"; +import ProductRelatedKeywordAssessor from "../relatedKeywordAssessor.js"; import ImageKeyphraseAssessment from "../../../assessments/seo/KeyphraseInImageTextAssessment.js"; - import { createAnchorOpeningTag } from "../../../../helpers"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher to use for the analysis. - * @param {Object?} options The options for this assessor. - * - * @constructor + * The CollectionCornerstoneRelatedKeywordAssessor class is used for the related keyword analysis for cornerstone products. */ -const ProductCornerStoneRelatedKeywordAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "productPageCornerstoneRelatedKeywordAssessor"; +export default class ProductCornerstoneRelatedKeywordAssessor extends ProductRelatedKeywordAssessor { + /** + * Creates a new ProductCornerstoneRelatedKeywordAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "productPageCornerstoneRelatedKeywordAssessor"; - this._assessments = [ - new IntroductionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( options.introductionKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.introductionKeyphraseCTAUrl ), - } ), - new KeyphraseLengthAssessment( { - parameters: { - recommendedMinimum: 4, - recommendedMaximum: 6, - acceptableMaximum: 8, - acceptableMinimum: 2, - }, - isRelatedKeyphrase: true, - urlTitle: createAnchorOpeningTag( options.keyphraseLengthUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.keyphraseLengthCTAUrl ), - }, true ), - new KeyphraseDensityAssessment( { - urlTitle: createAnchorOpeningTag( options.keyphraseDensityUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.keyphraseDensityCTAUrl ), - } ), - new MetaDescriptionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( options.metaDescriptionKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.metaDescriptionKeyphraseCTAUrl ), - } ), - new TextCompetingLinksAssessment( { - urlTitle: createAnchorOpeningTag( options.textCompetingLinksUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.textCompetingLinksCTAUrl ), - } ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( options.functionWordsInKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.functionWordsInKeyphraseCTAUrl ), - } ), - new ImageKeyphraseAssessment( { - scores: { - withAltNonKeyword: 3, - withAlt: 3, - noAlt: 3, - }, + this.addAssessment( "imageKeyphrase", new ImageKeyphraseAssessment( { + scores: { withAltNonKeyword: 3, withAlt: 3, noAlt: 3 }, urlTitle: createAnchorOpeningTag( options.imageKeyphraseUrlTitle ), urlCallToAction: createAnchorOpeningTag( options.imageKeyphraseCTAUrl ), - } ), - ]; -}; - -inherits( ProductCornerStoneRelatedKeywordAssessor, Assessor ); - -export default ProductCornerStoneRelatedKeywordAssessor; + } ) ); + } +} diff --git a/packages/yoastseo/src/scoring/assessors/productPages/cornerstone/seoAssessor.js b/packages/yoastseo/src/scoring/assessors/productPages/cornerstone/seoAssessor.js index 996a77bf41e..8a6895cbf01 100644 --- a/packages/yoastseo/src/scoring/assessors/productPages/cornerstone/seoAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/productPages/cornerstone/seoAssessor.js @@ -1,159 +1,47 @@ -import { inherits } from "util"; - -import Assessor from "../../assessor.js"; -import SeoAssessor from "../../seoAssessor.js"; -import IntroductionKeywordAssessment from "../../../assessments/seo/IntroductionKeywordAssessment.js"; -import KeyphraseLengthAssessment from "../../../assessments/seo/KeyphraseLengthAssessment.js"; -import KeyphraseDensityAssessment from "../../../assessments/seo/KeywordDensityAssessment.js"; -import MetaDescriptionKeywordAssessment from "../../../assessments/seo/MetaDescriptionKeywordAssessment.js"; +import ProductSEOAssessor from "../seoAssessor.js"; import MetaDescriptionLengthAssessment from "../../../assessments/seo/MetaDescriptionLengthAssessment.js"; -import SubheadingsKeywordAssessment from "../../../assessments/seo/SubHeadingsKeywordAssessment.js"; -import TextCompetingLinksAssessment from "../../../assessments/seo/TextCompetingLinksAssessment.js"; import TextLengthAssessment from "../../../assessments/seo/TextLengthAssessment.js"; -import KeyphraseInSEOTitleAssessment from "../../../assessments/seo/KeyphraseInSEOTitleAssessment.js"; -import PageTitleWidthAssessment from "../../../assessments/seo/PageTitleWidthAssessment.js"; import SlugKeywordAssessment from "../../../assessments/seo/UrlKeywordAssessment.js"; -import FunctionWordsInKeyphraseAssessment from "../../../assessments/seo/FunctionWordsInKeyphraseAssessment.js"; -import SingleH1Assessment from "../../../assessments/seo/SingleH1Assessment.js"; -import ImageCountAssessment from "../../../assessments/seo/ImageCountAssessment.js"; import ImageKeyphraseAssessment from "../../../assessments/seo/KeyphraseInImageTextAssessment.js"; -import ImageAltTagsAssessment from "../../../assessments/seo/ImageAltTagsAssessment.js"; -import ProductIdentifiersAssessment from "../../../assessments/seo/ProductIdentifiersAssessment.js"; -import ProductSKUAssessment from "../../../assessments/seo/ProductSKUAssessment.js"; - import { createAnchorOpeningTag } from "../../../../helpers"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher to use for the analysis. - * @param {Object?} options The options for this assessor. - * - * @constructor + * The ProductCornerstoneSEOAssessor class is used for the SEO analysis for cornerstone products. */ -const ProductCornerstoneSEOAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "productCornerstoneSEOAssessor"; +export default class ProductCornerstoneSEOAssessor extends ProductSEOAssessor { + /** + * Creates a new ProductCornerstoneSEOAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "productCornerstoneSEOAssessor"; - this._assessments = [ - new IntroductionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( options.introductionKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.introductionKeyphraseCTAUrl ), - } ), - new KeyphraseLengthAssessment( { - parameters: { - recommendedMinimum: 4, - recommendedMaximum: 6, - acceptableMaximum: 8, - acceptableMinimum: 2, - }, - urlTitle: createAnchorOpeningTag( options.keyphraseLengthUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.keyphraseLengthCTAUrl ), - }, true ), - new KeyphraseDensityAssessment( { - urlTitle: createAnchorOpeningTag( options.keyphraseDensityUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.keyphraseDensityCTAUrl ), - } ), - new MetaDescriptionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( options.metaDescriptionKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.metaDescriptionKeyphraseCTAUrl ), - } ), - new MetaDescriptionLengthAssessment( { - scores: { - tooLong: 3, - tooShort: 3, - }, + this.addAssessment( "metaDescriptionLength", new MetaDescriptionLengthAssessment( { + scores: { tooLong: 3, tooShort: 3 }, urlTitle: createAnchorOpeningTag( options.metaDescriptionLengthUrlTitle ), urlCallToAction: createAnchorOpeningTag( options.metaDescriptionLengthCTAUrl ), - } ), - new SubheadingsKeywordAssessment( { - urlTitle: createAnchorOpeningTag( options.subheadingsKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.subheadingsKeyphraseCTAUrl ), - } ), - new TextCompetingLinksAssessment( { - urlTitle: createAnchorOpeningTag( options.textCompetingLinksUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.textCompetingLinksCTAUrl ), - } ), - new TextLengthAssessment( { + } ) ); + this.addAssessment( "textLength", new TextLengthAssessment( { recommendedMinimum: 400, slightlyBelowMinimum: 300, belowMinimum: 200, - - scores: { - belowMinimum: -20, - farBelowMinimum: -20, - }, + scores: { belowMinimum: -20, farBelowMinimum: -20 }, urlTitle: createAnchorOpeningTag( options.textLengthUrlTitle ), urlCallToAction: createAnchorOpeningTag( options.textLengthCTAUrl ), cornerstoneContent: true, customContentType: this.type, - } ), - new KeyphraseInSEOTitleAssessment( { - urlTitle: createAnchorOpeningTag( options.titleKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.titleKeyphraseCTAUrl ), - } ), - new PageTitleWidthAssessment( { - scores: { - widthTooShort: 9, - }, - urlTitle: createAnchorOpeningTag( options.titleWidthUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.titleWidthCTAUrl ), - }, true ), - new SlugKeywordAssessment( - { - scores: { - okay: 3, - }, - urlTitle: createAnchorOpeningTag( options.urlKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.urlKeyphraseCTAUrl ), - } - ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( options.functionWordsInKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.functionWordsInKeyphraseCTAUrl ), - } ), - new SingleH1Assessment( { - urlTitle: createAnchorOpeningTag( options.singleH1UrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.singleH1CTAUrl ), - } ), - new ImageCountAssessment( { - scores: { - okay: 6, - }, - recommendedCount: 4, - urlTitle: createAnchorOpeningTag( options.imageCountUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.imageCountCTAUrl ), - }, options.countVideos ), - new ImageKeyphraseAssessment( { - scores: { - withAltNonKeyword: 3, - withAlt: 3, - noAlt: 3, - }, + } ) ); + this.addAssessment( "slugKeyword", new SlugKeywordAssessment( { + scores: { okay: 3 }, + urlTitle: createAnchorOpeningTag( options.urlKeyphraseUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.urlKeyphraseCTAUrl ), + } ) ); + this.addAssessment( "imageKeyphrase", new ImageKeyphraseAssessment( { + scores: { withAltNonKeyword: 3, withAlt: 3, noAlt: 3 }, urlTitle: createAnchorOpeningTag( options.imageKeyphraseUrlTitle ), urlCallToAction: createAnchorOpeningTag( options.imageKeyphraseCTAUrl ), - } ), - new ImageAltTagsAssessment( { - urlTitle: createAnchorOpeningTag( options.imageAltTagsUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.imageAltTagsCTAUrl ), - } ), - new ProductIdentifiersAssessment( { - urlTitle: createAnchorOpeningTag( options.productIdentifierUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.productIdentifierCTAUrl ), - assessVariants: options.assessVariants, - productIdentifierOrBarcode: options.productIdentifierOrBarcode, - shouldShowEditButton: options.shouldShowEditButtons, - } ), - new ProductSKUAssessment( { - urlTitle: createAnchorOpeningTag( options.productSKUUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.productSKUCTAUrl ), - assessVariants: options.assessVariants, - addSKULocation: options.addSKULocation, - shouldShowEditButton: options.shouldShowEditButtons, - } ), - ]; -}; - -inherits( ProductCornerstoneSEOAssessor, SeoAssessor ); - -export default ProductCornerstoneSEOAssessor; + } ) ); + } +} diff --git a/packages/yoastseo/src/scoring/assessors/productPages/relatedKeywordAssessor.js b/packages/yoastseo/src/scoring/assessors/productPages/relatedKeywordAssessor.js index c0fc7081c5f..5f3da6c4c11 100644 --- a/packages/yoastseo/src/scoring/assessors/productPages/relatedKeywordAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/productPages/relatedKeywordAssessor.js @@ -1,6 +1,4 @@ -import { inherits } from "util"; - -import Assessor from "../assessor.js"; +import RelatedKeywordAssessor from "../relatedKeywordAssessor.js"; import IntroductionKeywordAssessment from "../../assessments/seo/IntroductionKeywordAssessment.js"; import KeyphraseLengthAssessment from "../../assessments/seo/KeyphraseLengthAssessment.js"; import KeyphraseDensityAssessment from "../../assessments/seo/KeywordDensityAssessment.js"; @@ -8,60 +6,57 @@ import MetaDescriptionKeywordAssessment from "../../assessments/seo/MetaDescript import TextCompetingLinksAssessment from "../../assessments/seo/TextCompetingLinksAssessment.js"; import FunctionWordsInKeyphraseAssessment from "../../assessments/seo/FunctionWordsInKeyphraseAssessment.js"; import ImageKeyphraseAssessment from "../../assessments/seo/KeyphraseInImageTextAssessment.js"; - import { createAnchorOpeningTag } from "../../../helpers"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher to use for the analysis. - * @param {Object?} options The options for this assessor. - * - * @constructor + * The ProductRelatedKeywordAssessor class is used for the related keyword analysis for products. */ -const ProductRelatedKeywordAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "productPageRelatedKeywordAssessor"; - - this._assessments = [ - new IntroductionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( options.introductionKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.introductionKeyphraseCTAUrl ), - } ), - new KeyphraseLengthAssessment( { - parameters: { - recommendedMinimum: 4, - recommendedMaximum: 6, - acceptableMaximum: 8, - acceptableMinimum: 2, - }, - isRelatedKeyphrase: true, - urlTitle: createAnchorOpeningTag( options.keyphraseLengthUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.keyphraseLengthCTAUrl ), - }, true ), - new KeyphraseDensityAssessment( { - urlTitle: createAnchorOpeningTag( options.keyphraseDensityUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.keyphraseDensityCTAUrl ), - } ), - new MetaDescriptionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( options.metaDescriptionKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.metaDescriptionKeyphraseCTAUrl ), - } ), - new TextCompetingLinksAssessment( { - urlTitle: createAnchorOpeningTag( options.textCompetingLinksUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.textCompetingLinksCTAUrl ), - } ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( options.functionWordsInKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.functionWordsInKeyphraseCTAUrl ), - } ), - new ImageKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( options.imageKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.imageKeyphraseCTAUrl ), - } ), - ]; -}; - -inherits( ProductRelatedKeywordAssessor, Assessor ); +export default class ProductRelatedKeywordAssessor extends RelatedKeywordAssessor { + /** + * Creates a new ProductRelatedKeywordAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "productPageRelatedKeywordAssessor"; -export default ProductRelatedKeywordAssessor; + this._assessments = [ + new IntroductionKeywordAssessment( { + urlTitle: createAnchorOpeningTag( options.introductionKeyphraseUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.introductionKeyphraseCTAUrl ), + } ), + new KeyphraseLengthAssessment( { + parameters: { + recommendedMinimum: 4, + recommendedMaximum: 6, + acceptableMaximum: 8, + acceptableMinimum: 2, + }, + isRelatedKeyphrase: true, + urlTitle: createAnchorOpeningTag( options.keyphraseLengthUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.keyphraseLengthCTAUrl ), + }, true ), + new KeyphraseDensityAssessment( { + urlTitle: createAnchorOpeningTag( options.keyphraseDensityUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.keyphraseDensityCTAUrl ), + } ), + new MetaDescriptionKeywordAssessment( { + urlTitle: createAnchorOpeningTag( options.metaDescriptionKeyphraseUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.metaDescriptionKeyphraseCTAUrl ), + } ), + new TextCompetingLinksAssessment( { + urlTitle: createAnchorOpeningTag( options.textCompetingLinksUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.textCompetingLinksCTAUrl ), + } ), + new FunctionWordsInKeyphraseAssessment( { + urlTitle: createAnchorOpeningTag( options.functionWordsInKeyphraseUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.functionWordsInKeyphraseCTAUrl ), + } ), + new ImageKeyphraseAssessment( { + urlTitle: createAnchorOpeningTag( options.imageKeyphraseUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.imageKeyphraseCTAUrl ), + } ), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/assessors/productPages/seoAssessor.js b/packages/yoastseo/src/scoring/assessors/productPages/seoAssessor.js index c205ad23842..824f3024976 100644 --- a/packages/yoastseo/src/scoring/assessors/productPages/seoAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/productPages/seoAssessor.js @@ -1,6 +1,4 @@ -import { inherits } from "util"; - -import Assessor from "../assessor.js"; +import SEOAssessor from "../seoAssessor"; import IntroductionKeywordAssessment from "../../assessments/seo/IntroductionKeywordAssessment.js"; import KeyphraseLengthAssessment from "../../assessments/seo/KeyphraseLengthAssessment.js"; import KeyphraseDensityAssessment from "../../assessments/seo/KeywordDensityAssessment.js"; @@ -19,121 +17,118 @@ import ImageCountAssessment from "../../assessments/seo/ImageCountAssessment.js" import ImageAltTagsAssessment from "../../assessments/seo/ImageAltTagsAssessment.js"; import ProductIdentifiersAssessment from "../../assessments/seo/ProductIdentifiersAssessment.js"; import ProductSKUAssessment from "../../assessments/seo/ProductSKUAssessment.js"; - import { createAnchorOpeningTag } from "../../../helpers"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher to use for the analysis. - * @param {Object?} options The options for this assessor. - * - * @constructor + * The ProductSEOAssessor class is used for the SEO analysis for products. */ -const ProductSEOAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "productSEOAssessor"; - - this._assessments = [ - new IntroductionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( options.introductionKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.introductionKeyphraseCTAUrl ), - } ), - new KeyphraseLengthAssessment( { - parameters: { - recommendedMinimum: 4, - recommendedMaximum: 6, - acceptableMaximum: 8, - acceptableMinimum: 2, - }, - urlTitle: createAnchorOpeningTag( options.keyphraseLengthUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.keyphraseLengthCTAUrl ), - }, true ), - new KeyphraseDensityAssessment( { - urlTitle: createAnchorOpeningTag( options.keyphraseDensityUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.keyphraseDensityCTAUrl ), - } ), - new MetaDescriptionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( options.metaDescriptionKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.metaDescriptionKeyphraseCTAUrl ), - } ), - new MetaDescriptionLengthAssessment( { - urlTitle: createAnchorOpeningTag( options.metaDescriptionLengthUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.metaDescriptionLengthCTAUrl ), - } ), - new SubheadingsKeywordAssessment( { - urlTitle: createAnchorOpeningTag( options.subheadingsKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.subheadingsKeyphraseCTAUrl ), - } ), - new TextCompetingLinksAssessment( { - urlTitle: createAnchorOpeningTag( options.textCompetingLinksUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.textCompetingLinksCTAUrl ), - } ), - new TextLengthAssessment( { - recommendedMinimum: 200, - slightlyBelowMinimum: 150, - belowMinimum: 100, - veryFarBelowMinimum: 50, - urlTitle: createAnchorOpeningTag( options.textLengthUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.textLengthCTAUrl ), - customContentType: this.type, - } ), - new KeyphraseInSEOTitleAssessment( { - urlTitle: createAnchorOpeningTag( options.titleKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.titleKeyphraseCTAUrl ), - } ), - new PageTitleWidthAssessment( { - scores: { - widthTooShort: 9, - }, - urlTitle: createAnchorOpeningTag( options.titleWidthUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.titleWidthCTAUrl ), - }, true ), - new SlugKeywordAssessment( { - urlTitle: createAnchorOpeningTag( options.urlKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.urlKeyphraseCTAUrl ), - } ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( options.functionWordsInKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.functionWordsInKeyphraseCTAUrl ), - } ), - new SingleH1Assessment( { - urlTitle: createAnchorOpeningTag( options.singleH1UrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.singleH1CTAUrl ), - } ), - new ImageCountAssessment( { - scores: { - okay: 6, - }, - recommendedCount: 4, - urlTitle: createAnchorOpeningTag( options.imageCountUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.imageCountCTAUrl ), - }, options.countVideos ), - new ImageKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( options.imageKeyphraseUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.imageKeyphraseCTAUrl ), - } ), - new ImageAltTagsAssessment( { - urlTitle: createAnchorOpeningTag( options.imageAltTagsUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.imageAltTagsCTAUrl ), - } ), - new ProductIdentifiersAssessment( { - urlTitle: createAnchorOpeningTag( options.productIdentifierUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.productIdentifierCTAUrl ), - assessVariants: options.assessVariants, - productIdentifierOrBarcode: options.productIdentifierOrBarcode, - shouldShowEditButton: options.shouldShowEditButtons, - } ), - new ProductSKUAssessment( { - urlTitle: createAnchorOpeningTag( options.productSKUUrlTitle ), - urlCallToAction: createAnchorOpeningTag( options.productSKUCTAUrl ), - assessVariants: options.assessVariants, - addSKULocation: options.addSKULocation, - shouldShowEditButton: options.shouldShowEditButtons, - } ), - ]; -}; - -inherits( ProductSEOAssessor, Assessor ); +export default class ProductSEOAssessor extends SEOAssessor { + /** + * Creates a new ProductSEOAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "productSEOAssessor"; -export default ProductSEOAssessor; + this._assessments = [ + new IntroductionKeywordAssessment( { + urlTitle: createAnchorOpeningTag( options.introductionKeyphraseUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.introductionKeyphraseCTAUrl ), + } ), + new KeyphraseLengthAssessment( { + parameters: { + recommendedMinimum: 4, + recommendedMaximum: 6, + acceptableMaximum: 8, + acceptableMinimum: 2, + }, + urlTitle: createAnchorOpeningTag( options.keyphraseLengthUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.keyphraseLengthCTAUrl ), + }, true ), + new KeyphraseDensityAssessment( { + urlTitle: createAnchorOpeningTag( options.keyphraseDensityUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.keyphraseDensityCTAUrl ), + } ), + new MetaDescriptionKeywordAssessment( { + urlTitle: createAnchorOpeningTag( options.metaDescriptionKeyphraseUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.metaDescriptionKeyphraseCTAUrl ), + } ), + new MetaDescriptionLengthAssessment( { + urlTitle: createAnchorOpeningTag( options.metaDescriptionLengthUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.metaDescriptionLengthCTAUrl ), + } ), + new SubheadingsKeywordAssessment( { + urlTitle: createAnchorOpeningTag( options.subheadingsKeyphraseUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.subheadingsKeyphraseCTAUrl ), + } ), + new TextCompetingLinksAssessment( { + urlTitle: createAnchorOpeningTag( options.textCompetingLinksUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.textCompetingLinksCTAUrl ), + } ), + new TextLengthAssessment( { + recommendedMinimum: 200, + slightlyBelowMinimum: 150, + belowMinimum: 100, + veryFarBelowMinimum: 50, + urlTitle: createAnchorOpeningTag( options.textLengthUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.textLengthCTAUrl ), + customContentType: this.type, + } ), + new KeyphraseInSEOTitleAssessment( { + urlTitle: createAnchorOpeningTag( options.titleKeyphraseUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.titleKeyphraseCTAUrl ), + } ), + new PageTitleWidthAssessment( { + scores: { + widthTooShort: 9, + }, + urlTitle: createAnchorOpeningTag( options.titleWidthUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.titleWidthCTAUrl ), + }, true ), + new SlugKeywordAssessment( { + urlTitle: createAnchorOpeningTag( options.urlKeyphraseUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.urlKeyphraseCTAUrl ), + } ), + new FunctionWordsInKeyphraseAssessment( { + urlTitle: createAnchorOpeningTag( options.functionWordsInKeyphraseUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.functionWordsInKeyphraseCTAUrl ), + } ), + new SingleH1Assessment( { + urlTitle: createAnchorOpeningTag( options.singleH1UrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.singleH1CTAUrl ), + } ), + new ImageCountAssessment( { + scores: { + okay: 6, + }, + recommendedCount: 4, + urlTitle: createAnchorOpeningTag( options.imageCountUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.imageCountCTAUrl ), + }, options.countVideos ), + new ImageKeyphraseAssessment( { + urlTitle: createAnchorOpeningTag( options.imageKeyphraseUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.imageKeyphraseCTAUrl ), + } ), + new ImageAltTagsAssessment( { + urlTitle: createAnchorOpeningTag( options.imageAltTagsUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.imageAltTagsCTAUrl ), + } ), + new ProductIdentifiersAssessment( { + urlTitle: createAnchorOpeningTag( options.productIdentifierUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.productIdentifierCTAUrl ), + assessVariants: options.assessVariants, + productIdentifierOrBarcode: options.productIdentifierOrBarcode, + shouldShowEditButton: options.shouldShowEditButtons, + } ), + new ProductSKUAssessment( { + urlTitle: createAnchorOpeningTag( options.productSKUUrlTitle ), + urlCallToAction: createAnchorOpeningTag( options.productSKUCTAUrl ), + assessVariants: options.assessVariants, + addSKULocation: options.addSKULocation, + shouldShowEditButton: options.shouldShowEditButtons, + } ), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/assessors/storeBlog/cornerstone/seoAssessor.js b/packages/yoastseo/src/scoring/assessors/storeBlog/cornerstone/seoAssessor.js index e4655fd2f22..79f1c1a0425 100644 --- a/packages/yoastseo/src/scoring/assessors/storeBlog/cornerstone/seoAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/storeBlog/cornerstone/seoAssessor.js @@ -1,74 +1,31 @@ -import { inherits } from "util"; - -import Assessor from "../../assessor.js"; -import SeoAssessor from "../../seoAssessor.js"; -import KeyphraseLengthAssessment from "../../../assessments/seo/KeyphraseLengthAssessment.js"; -import MetaDescriptionKeywordAssessment from "../../../assessments/seo/MetaDescriptionKeywordAssessment.js"; +import StoreBlogSEOAssessor from "../seoAssessor.js"; import MetaDescriptionLengthAssessment from "../../../assessments/seo/MetaDescriptionLengthAssessment.js"; -import KeyphraseInSEOTitleAssessment from "../../../assessments/seo/KeyphraseInSEOTitleAssessment.js"; -import PageTitleWidthAssessment from "../../../assessments/seo/PageTitleWidthAssessment.js"; import SlugKeywordAssessment from "../../../assessments/seo/UrlKeywordAssessment.js"; -import FunctionWordsInKeyphraseAssessment from "../../../assessments/seo/FunctionWordsInKeyphraseAssessment.js"; - import { createAnchorOpeningTag } from "../../../../helpers"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher used for the analysis. - * @param {Object?} options The options for this assessor. - * @param {Function} options.marker The marker to pass the list of marks to. - * - * @constructor + * The StoreBlogCornerstoneSEOAssessor class is used for the SEO analysis for cornerstone store blogs. */ -const StoreBlogCornerstoneSEOAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "storeBlogCornerstoneSEOAssessor"; +export default class StoreBlogCornerstoneSEOAssessor extends StoreBlogSEOAssessor { + /** + * Creates a new StoreBlogCornerstoneSEOAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "storeBlogCornerstoneSEOAssessor"; - this._assessments = [ - new KeyphraseLengthAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), - } ), - new MetaDescriptionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), - } ), - new MetaDescriptionLengthAssessment( { - scores: { - tooLong: 3, - tooShort: 3, - }, + this.addAssessment( "metaDescriptionLength", new MetaDescriptionLengthAssessment( { + scores: { tooLong: 3, tooShort: 3 }, urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify46" ), urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify47" ), - } ), - new KeyphraseInSEOTitleAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify24" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify25" ), - } ), - new PageTitleWidthAssessment( { - scores: { - widthTooShort: 9, - }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify52" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify53" ), - }, true ), - new SlugKeywordAssessment( - { - scores: { - okay: 3, - }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify26" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify27" ), - } - ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), - } ), - ]; -}; - -inherits( StoreBlogCornerstoneSEOAssessor, SeoAssessor ); + } ) ); -export default StoreBlogCornerstoneSEOAssessor; + this.addAssessment( "slugKeyword", new SlugKeywordAssessment( { + scores: { okay: 3 }, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify26" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify27" ), + } ) ); + } +} diff --git a/packages/yoastseo/src/scoring/assessors/storeBlog/seoAssessor.js b/packages/yoastseo/src/scoring/assessors/storeBlog/seoAssessor.js index b47691c56d0..4426fdfc449 100644 --- a/packages/yoastseo/src/scoring/assessors/storeBlog/seoAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/storeBlog/seoAssessor.js @@ -1,6 +1,4 @@ -import { inherits } from "util"; - -import Assessor from "../assessor"; +import SEOAssessor from "../seoAssessor"; import KeyphraseLengthAssessment from "../../assessments/seo/KeyphraseLengthAssessment"; import MetaDescriptionKeywordAssessment from "../../assessments/seo/MetaDescriptionKeywordAssessment"; import MetaDescriptionLengthAssessment from "../../assessments/seo/MetaDescriptionLengthAssessment"; @@ -8,57 +6,53 @@ import KeyphraseInSEOTitleAssessment from "../../assessments/seo/KeyphraseInSEOT import PageTitleWidthAssessment from "../../assessments/seo/PageTitleWidthAssessment"; import SlugKeywordAssessment from "../../assessments/seo/UrlKeywordAssessment"; import FunctionWordsInKeyphraseAssessment from "../../assessments/seo/FunctionWordsInKeyphraseAssessment"; - import { createAnchorOpeningTag } from "../../../helpers"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher to use for the analysis. - * @param {Object?} options The options for this assessor. - * @param {Function} options.marker The marker to pass the list of marks to. - * - * @constructor + * The StoreBlogSEOAssessor class is used for the SEO analysis for store blogs. */ -const StoreBlogSEOAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "storeBlogSEOAssessor"; - - this._assessments = [ - new KeyphraseLengthAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), - } ), - new MetaDescriptionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), - } ), - new MetaDescriptionLengthAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify46" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify47" ), - } ), - new KeyphraseInSEOTitleAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify24" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify25" ), - } ), - new PageTitleWidthAssessment( { - scores: { - widthTooShort: 9, - }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify52" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify53" ), - }, true ), - new SlugKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify26" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify27" ), - } ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), - } ), - ]; -}; - -inherits( StoreBlogSEOAssessor, Assessor ); +export default class StoreBlogSEOAssessor extends SEOAssessor { + /** + * Creates a new StoreBlogSEOAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "storeBlogSEOAssessor"; -export default StoreBlogSEOAssessor; + this._assessments = [ + new KeyphraseLengthAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), + } ), + new MetaDescriptionKeywordAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), + } ), + new MetaDescriptionLengthAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify46" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify47" ), + } ), + new KeyphraseInSEOTitleAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify24" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify25" ), + } ), + new PageTitleWidthAssessment( { + scores: { + widthTooShort: 9, + }, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify52" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify53" ), + }, true ), + new SlugKeywordAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify26" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify27" ), + } ), + new FunctionWordsInKeyphraseAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), + } ), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/assessors/storePostsAndPages/contentAssessor.js b/packages/yoastseo/src/scoring/assessors/storePostsAndPages/contentAssessor.js index 030d116f015..85a7567dffa 100644 --- a/packages/yoastseo/src/scoring/assessors/storePostsAndPages/contentAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/storePostsAndPages/contentAssessor.js @@ -1,6 +1,3 @@ -import { inherits } from "util"; - -import Assessor from "../assessor.js"; import ContentAssessor from "../contentAssessor.js"; import SubheadingDistributionTooLongAssessment from "../../assessments/readability/SubheadingDistributionTooLongAssessment.js"; import ParagraphTooLongAssessment from "../../assessments/readability/ParagraphTooLongAssessment.js"; @@ -12,50 +9,47 @@ import SentenceBeginningsAssessment from "../../assessments/readability/Sentence import { createAnchorOpeningTag } from "../../../helpers"; /** - * Creates the Assessor for e-commerce posts and pages content types. - * - * @param {object} researcher The researcher to use for the analysis. - * @param {Object} options The options for this assessor. - * - * @constructor + * The StorePostsAndPagesContentAssessor class is used for the readability analysis for store posts and pages. */ -const StorePostsAndPagesContentAssessor = function( researcher, options = {} ) { - Assessor.call( this, researcher, options ); - this.type = "storePostsAndPagesContentAssessor"; - this._assessments = [ - - new SubheadingDistributionTooLongAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify68" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify69" ), - } ), - new ParagraphTooLongAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify66" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify67" ), - } ), - new SentenceLengthInTextAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify48" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify49" ), - } ), - new TransitionWordsAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify44" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify45" ), - } ), - new PassiveVoiceAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify42" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify43" ), - } ), - new TextPresenceAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify56" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify57" ), - } ), - new SentenceBeginningsAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify5" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify65" ), - } ), - ]; -}; - -inherits( StorePostsAndPagesContentAssessor, ContentAssessor ); - -export default StorePostsAndPagesContentAssessor; +export default class StorePostsAndPagesContentAssessor extends ContentAssessor { + /** + * Creates a new StorePostsAndPagesContentAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "storePostsAndPagesContentAssessor"; + this._assessments = [ + new SubheadingDistributionTooLongAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify68" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify69" ), + } ), + new ParagraphTooLongAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify66" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify67" ), + } ), + new SentenceLengthInTextAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify48" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify49" ), + } ), + new TransitionWordsAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify44" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify45" ), + } ), + new PassiveVoiceAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify42" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify43" ), + } ), + new TextPresenceAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify56" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify57" ), + } ), + new SentenceBeginningsAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify5" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify65" ), + } ), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/assessors/storePostsAndPages/cornerstone/contentAssessor.js b/packages/yoastseo/src/scoring/assessors/storePostsAndPages/cornerstone/contentAssessor.js index b707095ef39..3914201ba0d 100644 --- a/packages/yoastseo/src/scoring/assessors/storePostsAndPages/cornerstone/contentAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/storePostsAndPages/cornerstone/contentAssessor.js @@ -1,76 +1,31 @@ -import { createAnchorOpeningTag } from "../../../../helpers"; - -import ContentAssessor from "../../contentAssessor.js"; +import ContentAssessor from "../contentAssessor.js"; import SubheadingDistributionTooLongAssessment from "../../../assessments/readability/SubheadingDistributionTooLongAssessment.js"; -import ParagraphTooLongAssessment from "../../../assessments/readability/ParagraphTooLongAssessment.js"; import SentenceLengthInTextAssessment from "../../../assessments/readability/SentenceLengthInTextAssessment.js"; -import TransitionWordsAssessment from "../../../assessments/readability/TransitionWordsAssessment.js"; -import PassiveVoiceAssessment from "../../../assessments/readability/PassiveVoiceAssessment.js"; -import TextPresenceAssessment from "../../../assessments/readability/TextPresenceAssessment.js"; -import SentenceBeginningsAssessment from "../../../assessments/readability/SentenceBeginningsAssessment.js"; +import { createAnchorOpeningTag } from "../../../../helpers"; /** - * Creates the Assessor - * - * @param {object} researcher The researcher used for the analysis. - * @param {Object} options The options for this assessor. - * @param {Object} options.marker The marker to pass the list of marks to. - * - * @constructor + * The StorePostsAndPagesCornerstoneContentAssessor class is used for the readability analysis for cornerstone store posts and pages. */ -class StorePostsAndPagesCornerstoneContentAssessor extends ContentAssessor { +export default class StorePostsAndPagesCornerstoneContentAssessor extends ContentAssessor { /** - * Creates a new assessor. - * + * Creates a new StorePostsAndPagesCornerstoneContentAssessor instance. * @param {Researcher} researcher The researcher to use. - * @param {Object} options The assessor options. + * @param {Object} [options] The assessor options. */ constructor( researcher, options ) { super( researcher, options ); - this.type = "storePostsAndPagesCornerstoneContentAssessor"; - this._assessments = [ - - new SubheadingDistributionTooLongAssessment( { - parameters: { - slightlyTooMany: 250, - farTooMany: 300, - recommendedMaximumLength: 250, - }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify68" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify69" ), - cornerstoneContent: true, - } ), - new ParagraphTooLongAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify66" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify67" ), - } ), - new SentenceLengthInTextAssessment( { - slightlyTooMany: 20, - farTooMany: 25, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify48" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify49" ), - }, true ), - new TransitionWordsAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify44" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify45" ), - } ), - new PassiveVoiceAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify42" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify43" ), - } ), - new TextPresenceAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify56" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify57" ), - } ), - new SentenceBeginningsAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify5" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify65" ), - } ), - ]; + this.addAssessment( "subheadingsTooLong", new SubheadingDistributionTooLongAssessment( { + parameters: { slightlyTooMany: 250, farTooMany: 300, recommendedMaximumLength: 250 }, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify68" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify69" ), + cornerstoneContent: true, + } ) ); + this.addAssessment( "textSentenceLength", new SentenceLengthInTextAssessment( { + slightlyTooMany: 20, farTooMany: 25, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify48" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify49" ), + }, true ) ); } } - -export default StorePostsAndPagesCornerstoneContentAssessor; - diff --git a/packages/yoastseo/src/scoring/assessors/storePostsAndPages/cornerstone/relatedKeywordAssessor.js b/packages/yoastseo/src/scoring/assessors/storePostsAndPages/cornerstone/relatedKeywordAssessor.js index 38bb3dc68ca..07906391634 100644 --- a/packages/yoastseo/src/scoring/assessors/storePostsAndPages/cornerstone/relatedKeywordAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/storePostsAndPages/cornerstone/relatedKeywordAssessor.js @@ -1,66 +1,24 @@ -import { inherits } from "util"; - -import Assessor from "../../assessor.js"; -import IntroductionKeywordAssessment from "../../../assessments/seo/IntroductionKeywordAssessment.js"; -import KeyphraseLengthAssessment from "../../../assessments/seo/KeyphraseLengthAssessment.js"; -import KeyphraseDensityAssessment from "../../../assessments/seo/KeywordDensityAssessment.js"; -import MetaDescriptionKeywordAssessment from "../../../assessments/seo/MetaDescriptionKeywordAssessment.js"; -import TextCompetingLinksAssessment from "../../../assessments/seo/TextCompetingLinksAssessment.js"; -import FunctionWordsInKeyphraseAssessment from "../../../assessments/seo/FunctionWordsInKeyphraseAssessment.js"; +import StorePostsAndPagesRelatedKeywordAssessor from "../relatedKeywordAssessor"; import ImageKeyphraseAssessment from "../../../assessments/seo/KeyphraseInImageTextAssessment.js"; import { createAnchorOpeningTag } from "../../../../helpers"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher used for the analysis. - * @param {Object?} options The options for this assessor. - * @param {Function} options.marker The marker to pass the list of marks to. - * - * @constructor + * The StorePostsAndPagesCornerstoneRelatedKeywordAssessor class is used for the related keyword analysis for cornerstone posts and pages. */ -const StorePostsAndPagesCornerstoneRelatedKeywordAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "storePostsAndPagesCornerstoneRelatedKeywordAssessor"; +export default class StorePostsAndPagesCornerstoneRelatedKeywordAssessor extends StorePostsAndPagesRelatedKeywordAssessor { + /** + * Creates a new StorePostsAndPagesCornerstoneRelatedKeywordAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "storePostsAndPagesCornerstoneRelatedKeywordAssessor"; - this._assessments = [ - new IntroductionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify8" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify9" ), - } ), - new KeyphraseLengthAssessment( { - isRelatedKeyphrase: true, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), - } ), - new KeyphraseDensityAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify12" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify13" ), - } ), - new MetaDescriptionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), - } ), - new TextCompetingLinksAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify18" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify19" ), - } ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), - } ), - new ImageKeyphraseAssessment( { - scores: { - withAltNonKeyword: 3, - withAlt: 3, - noAlt: 3, - }, + this.addAssessment( "imageKeyphrase", new ImageKeyphraseAssessment( { + scores: { withAltNonKeyword: 3, withAlt: 3, noAlt: 3 }, urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify22" ), urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify23" ), - } ), - ]; -}; - -inherits( StorePostsAndPagesCornerstoneRelatedKeywordAssessor, Assessor ); - -export default StorePostsAndPagesCornerstoneRelatedKeywordAssessor; + } ) ); + } +} diff --git a/packages/yoastseo/src/scoring/assessors/storePostsAndPages/cornerstone/seoAssessor.js b/packages/yoastseo/src/scoring/assessors/storePostsAndPages/cornerstone/seoAssessor.js index 3346a81ae78..0bec567734d 100644 --- a/packages/yoastseo/src/scoring/assessors/storePostsAndPages/cornerstone/seoAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/storePostsAndPages/cornerstone/seoAssessor.js @@ -1,141 +1,46 @@ -import { inherits } from "util"; - -import Assessor from "../../assessor.js"; -import SeoAssessor from "../../seoAssessor.js"; -import IntroductionKeywordAssessment from "../../../assessments/seo/IntroductionKeywordAssessment.js"; -import KeyphraseLengthAssessment from "../../../assessments/seo/KeyphraseLengthAssessment.js"; -import KeyphraseDensityAssessment from "../../../assessments/seo/KeywordDensityAssessment.js"; -import MetaDescriptionKeywordAssessment from "../../../assessments/seo/MetaDescriptionKeywordAssessment.js"; +import StorePostsAndPagesSEOAssessor from "../seoAssessor.js"; import MetaDescriptionLengthAssessment from "../../../assessments/seo/MetaDescriptionLengthAssessment.js"; -import SubheadingsKeywordAssessment from "../../../assessments/seo/SubHeadingsKeywordAssessment.js"; -import TextCompetingLinksAssessment from "../../../assessments/seo/TextCompetingLinksAssessment.js"; -import ImageKeyphraseAssessment from "../../../assessments/seo/KeyphraseInImageTextAssessment.js"; -import ImageCountAssessment from "../../../assessments/seo/ImageCountAssessment.js"; import TextLengthAssessment from "../../../assessments/seo/TextLengthAssessment.js"; -import OutboundLinksAssessment from "../../../assessments/seo/OutboundLinksAssessment.js"; -import KeyphraseInSEOTitleAssessment from "../../../assessments/seo/KeyphraseInSEOTitleAssessment.js"; -import InternalLinksAssessment from "../../../assessments/seo/InternalLinksAssessment.js"; -import PageTitleWidthAssessment from "../../../assessments/seo/PageTitleWidthAssessment.js"; import SlugKeywordAssessment from "../../../assessments/seo/UrlKeywordAssessment.js"; -import FunctionWordsInKeyphraseAssessment from "../../../assessments/seo/FunctionWordsInKeyphraseAssessment.js"; -import SingleH1Assessment from "../../../assessments/seo/SingleH1Assessment.js"; - +import ImageKeyphraseAssessment from "../../../assessments/seo/KeyphraseInImageTextAssessment"; import { createAnchorOpeningTag } from "../../../../helpers"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher used for the analysis. - * @param {Object?} options The options for this assessor. - * @param {Function} options.marker The marker to pass the list of marks to. - * - * @constructor + * The StorePostsAndPagesCornerstoneSEOAssessor class is used for the SEO analysis for cornerstone products. */ -const StorePostsAndPagesCornerstoneSEOAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "storePostsAndPagesCornerstoneSEOAssessor"; +export default class StorePostsAndPagesCornerstoneSEOAssessor extends StorePostsAndPagesSEOAssessor { + /** + * Creates a new StorePostsAndPagesCornerstoneSEOAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "storePostsAndPagesCornerstoneSEOAssessor"; - this._assessments = [ - new IntroductionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify8" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify9" ), - } ), - new KeyphraseLengthAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), - } ), - new KeyphraseDensityAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify12" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify13" ), - } ), - new MetaDescriptionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), - } ), - new MetaDescriptionLengthAssessment( { - scores: { - tooLong: 3, - tooShort: 3, - }, + this.addAssessment( "metaDescriptionLength", new MetaDescriptionLengthAssessment( { + scores: { tooLong: 3, tooShort: 3 }, urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify46" ), urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify47" ), - } ), - new SubheadingsKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify16" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify17" ), - } ), - new TextCompetingLinksAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify18" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify19" ), - } ), - new ImageKeyphraseAssessment( { - scores: { - withAltNonKeyword: 3, - withAlt: 3, - noAlt: 3, - }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify22" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify23" ), - } ), - new ImageCountAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify20" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify21" ), - } ), - new TextLengthAssessment( { + } ) ); + this.addAssessment( "textLength", new TextLengthAssessment( { recommendedMinimum: 900, slightlyBelowMinimum: 400, belowMinimum: 300, - - scores: { - belowMinimum: -20, - farBelowMinimum: -20, - }, + scores: { belowMinimum: -20, farBelowMinimum: -20 }, urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify58" ), urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify59" ), cornerstoneContent: true, - } ), - new OutboundLinksAssessment( { - scores: { - noLinks: 3, - }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify62" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify63" ), - } ), - new KeyphraseInSEOTitleAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify24" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify25" ), - } ), - new InternalLinksAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify60" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify61" ), - } ), - new PageTitleWidthAssessment( { - scores: { - widthTooShort: 9, - }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify52" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify53" ), - }, true ), - new SlugKeywordAssessment( - { - scores: { - okay: 3, - }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify26" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify27" ), - } - ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), - } ), - new SingleH1Assessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify54" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify55" ), - } ), - ]; -}; - -inherits( StorePostsAndPagesCornerstoneSEOAssessor, SeoAssessor ); - -export default StorePostsAndPagesCornerstoneSEOAssessor; + } ) ); + this.addAssessment( "slugKeyword", new SlugKeywordAssessment( { + scores: { okay: 3 }, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify26" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify27" ), + } ) ); + this.addAssessment( "imageKeyphrase", new ImageKeyphraseAssessment( { + scores: { withAltNonKeyword: 3, withAlt: 3, noAlt: 3 }, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify22" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify23" ), + } ) ); + } +} diff --git a/packages/yoastseo/src/scoring/assessors/storePostsAndPages/relatedKeywordAssessor.js b/packages/yoastseo/src/scoring/assessors/storePostsAndPages/relatedKeywordAssessor.js index 158b692c839..2f7696eac41 100644 --- a/packages/yoastseo/src/scoring/assessors/storePostsAndPages/relatedKeywordAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/storePostsAndPages/relatedKeywordAssessor.js @@ -1,6 +1,4 @@ -import { inherits } from "util"; - -import Assessor from "../assessor.js"; +import RelatedKeywordAssessor from "../relatedKeywordAssessor.js"; import IntroductionKeywordAssessment from "../../assessments/seo/IntroductionKeywordAssessment.js"; import KeyphraseLengthAssessment from "../../assessments/seo/KeyphraseLengthAssessment.js"; import KeyphraseDensityAssessment from "../../assessments/seo/KeywordDensityAssessment.js"; @@ -11,51 +9,48 @@ import ImageKeyphraseAssessment from "../../assessments/seo/KeyphraseInImageText import { createAnchorOpeningTag } from "../../../helpers"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher to use for the analysis. - * @param {Object?} options The options for this assessor. - * @param {Function} options.marker The marker to pass the list of marks to. - * - * @constructor + * The StorePostsAndPagesRelatedKeywordAssessor class is used for the related keyword analysis for store posts and pages. */ -const StorePostsAndPagesRelatedKeywordAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "storePostsAndPagesRelatedKeywordAssessor"; - - this._assessments = [ - new IntroductionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify8" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify9" ), - } ), - new KeyphraseLengthAssessment( { - isRelatedKeyphrase: true, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), - } ), - new KeyphraseDensityAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify12" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify13" ), - } ), - new MetaDescriptionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), - } ), - new TextCompetingLinksAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify18" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify19" ), - } ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), - } ), - new ImageKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify22" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify23" ), - } ), - ]; -}; - -inherits( StorePostsAndPagesRelatedKeywordAssessor, Assessor ); +export default class StorePostsAndPagesRelatedKeywordAssessor extends RelatedKeywordAssessor { + /** + * Creates a new StorePostsAndPagesRelatedKeywordAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "storePostsAndPagesRelatedKeywordAssessor"; -export default StorePostsAndPagesRelatedKeywordAssessor; + this._assessments = [ + new IntroductionKeywordAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify8" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify9" ), + } ), + new KeyphraseLengthAssessment( { + isRelatedKeyphrase: true, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), + } ), + new KeyphraseDensityAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify12" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify13" ), + } ), + new MetaDescriptionKeywordAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), + } ), + new TextCompetingLinksAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify18" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify19" ), + } ), + new FunctionWordsInKeyphraseAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), + } ), + new ImageKeyphraseAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify22" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify23" ), + } ), + ]; + } +} diff --git a/packages/yoastseo/src/scoring/assessors/storePostsAndPages/seoAssessor.js b/packages/yoastseo/src/scoring/assessors/storePostsAndPages/seoAssessor.js index d5eeef151b0..f2ab1eb5066 100644 --- a/packages/yoastseo/src/scoring/assessors/storePostsAndPages/seoAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/storePostsAndPages/seoAssessor.js @@ -1,6 +1,4 @@ -import { inherits } from "util"; - -import Assessor from "../assessor.js"; +import SEOAssessor from "../seoAssessor.js"; import IntroductionKeywordAssessment from "../../assessments/seo/IntroductionKeywordAssessment.js"; import KeyphraseLengthAssessment from "../../assessments/seo/KeyphraseLengthAssessment.js"; import KeyphraseDensityAssessment from "../../assessments/seo/KeywordDensityAssessment.js"; @@ -18,97 +16,93 @@ import InternalLinksAssessment from "../../assessments/seo/InternalLinksAssessme import PageTitleWidthAssessment from "../../assessments/seo/PageTitleWidthAssessment.js"; import SlugKeywordAssessment from "../../assessments/seo/UrlKeywordAssessment.js"; import SingleH1Assessment from "../../assessments/seo/SingleH1Assessment.js"; - import { createAnchorOpeningTag } from "../../../helpers"; /** - * Creates the Assessor - * - * @param {Researcher} researcher The researcher to use for the analysis. - * @param {Object?} options The options for this assessor. - * @param {Function} options.marker The marker to pass the list of marks to. - * - * @constructor + * The StorePostsAndPagesSEOAssessor class is used for the SEO analysis for store posts and pages. */ -const StorePostsAndPagesSEOAssessor = function( researcher, options ) { - Assessor.call( this, researcher, options ); - this.type = "storePostsAndPagesSEOAssessor"; - - this._assessments = [ - new IntroductionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify8" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify9" ), - } ), - new KeyphraseLengthAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), - } ), - new KeyphraseDensityAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify12" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify13" ), - } ), - new MetaDescriptionKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), - } ), - new MetaDescriptionLengthAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify46" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify47" ), - } ), - new SubheadingsKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify16" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify17" ), - } ), - new TextCompetingLinksAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify18" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify19" ), - } ), - new ImageKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify22" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify23" ), - } ), - new ImageCountAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify20" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify21" ), - } ), - new TextLengthAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify58" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify59" ), - } ), - new OutboundLinksAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify62" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify63" ), - } ), - new KeyphraseInSEOTitleAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify24" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify25" ), - } ), - new InternalLinksAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify60" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify61" ), - } ), - new PageTitleWidthAssessment( { - scores: { - widthTooShort: 9, - }, - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify52" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify53" ), - }, true ), - new SlugKeywordAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify26" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify27" ), - } ), - new FunctionWordsInKeyphraseAssessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), - } ), - new SingleH1Assessment( { - urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify54" ), - urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify55" ), - } ), - ]; -}; - -inherits( StorePostsAndPagesSEOAssessor, Assessor ); +export default class StorePostsAndPagesSEOAssessor extends SEOAssessor { + /** + * Creates a new StorePostsAndPagesSEOAssessor instance. + * @param {Researcher} researcher The researcher to use. + * @param {Object} [options] The assessor options. + */ + constructor( researcher, options ) { + super( researcher, options ); + this.type = "storePostsAndPagesSEOAssessor"; -export default StorePostsAndPagesSEOAssessor; + this._assessments = [ + new IntroductionKeywordAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify8" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify9" ), + } ), + new KeyphraseLengthAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify10" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify11" ), + } ), + new KeyphraseDensityAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify12" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify13" ), + } ), + new MetaDescriptionKeywordAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify14" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify15" ), + } ), + new MetaDescriptionLengthAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify46" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify47" ), + } ), + new SubheadingsKeywordAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify16" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify17" ), + } ), + new TextCompetingLinksAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify18" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify19" ), + } ), + new ImageKeyphraseAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify22" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify23" ), + } ), + new ImageCountAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify20" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify21" ), + } ), + new TextLengthAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify58" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify59" ), + } ), + new OutboundLinksAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify62" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify63" ), + } ), + new KeyphraseInSEOTitleAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify24" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify25" ), + } ), + new InternalLinksAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify60" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify61" ), + } ), + new PageTitleWidthAssessment( { + scores: { + widthTooShort: 9, + }, + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify52" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify53" ), + }, true ), + new SlugKeywordAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify26" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify27" ), + } ), + new FunctionWordsInKeyphraseAssessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify50" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify51" ), + } ), + new SingleH1Assessment( { + urlTitle: createAnchorOpeningTag( "https://yoa.st/shopify54" ), + urlCallToAction: createAnchorOpeningTag( "https://yoa.st/shopify55" ), + } ), + ]; + } +} From d6727ea055bbea184f8d31a01eba2023b9a04a2a Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 5 Sep 2024 16:03:07 +0200 Subject: [PATCH 057/313] Initial new dashboard page --- admin/class-admin-asset-manager.php | 5 + config/webpack/paths.js | 1 + packages/js/src/dashboard/app.js | 31 ++ packages/js/src/dashboard/constants/index.js | 5 + packages/js/src/dashboard/initialize.js | 32 ++ packages/js/src/dashboard/store/index.js | 49 +++ .../js/src/dashboard/store/preferences.js | 34 ++ .../general-page-integration.php | 169 ++++++++++ .../General_Page_Integration_Test.php | 296 ++++++++++++++++++ 9 files changed, 622 insertions(+) create mode 100644 packages/js/src/dashboard/app.js create mode 100644 packages/js/src/dashboard/constants/index.js create mode 100644 packages/js/src/dashboard/initialize.js create mode 100644 packages/js/src/dashboard/store/index.js create mode 100644 packages/js/src/dashboard/store/preferences.js create mode 100644 src/dashboard/user-interface/general-page-integration.php create mode 100644 tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php diff --git a/admin/class-admin-asset-manager.php b/admin/class-admin-asset-manager.php index ec0df47a1a3..09d269b7737 100644 --- a/admin/class-admin-asset-manager.php +++ b/admin/class-admin-asset-manager.php @@ -660,6 +660,11 @@ protected function styles_to_be_registered() { 'src' => 'academy-' . $flat_version, 'deps' => [ self::PREFIX . 'tailwind' ], ], + [ + 'name' => 'new-dashboard', + 'src' => 'new-dashboard-' . $flat_version, + 'deps' => [ self::PREFIX . 'tailwind' ], + ], [ 'name' => 'support', 'src' => 'support-' . $flat_version, diff --git a/config/webpack/paths.js b/config/webpack/paths.js index 28132d9f0bd..8633e3f9496 100644 --- a/config/webpack/paths.js +++ b/config/webpack/paths.js @@ -44,6 +44,7 @@ const getEntries = ( sourceDirectory = "./packages/js/src" ) => ( { settings: `${ sourceDirectory }/settings.js`, "new-settings": `${ sourceDirectory }/settings/initialize.js`, academy: `${ sourceDirectory }/academy/initialize.js`, + "new-dashboard": `${ sourceDirectory }/dashboard/initialize.js`, support: `${ sourceDirectory }/support/initialize.js`, "how-to-block": `${ sourceDirectory }/structured-data-blocks/how-to/block.js`, "faq-block": `${ sourceDirectory }/structured-data-blocks/faq/block.js`, diff --git a/packages/js/src/dashboard/app.js b/packages/js/src/dashboard/app.js new file mode 100644 index 00000000000..1544f134d07 --- /dev/null +++ b/packages/js/src/dashboard/app.js @@ -0,0 +1,31 @@ +/* eslint-disable complexity */ + +import { __ } from "@wordpress/i18n"; +import { Paper, Title } from "@yoast/ui-library"; + +/** + * @returns {JSX.Element} The app component. + */ +const App = () => { + return ( +
+ +
+
+ { __( "Alert center", "wordpress-seo" ) } +

+ { __( "Monitor and manage potential SEO problems affecting your site and stay informed with important notifications and updates.", "wordpress-seo" ) } +

+
+
+
+
+ Content +
+
+
+
+ ); +}; + +export default App; diff --git a/packages/js/src/dashboard/constants/index.js b/packages/js/src/dashboard/constants/index.js new file mode 100644 index 00000000000..fa08f25daa4 --- /dev/null +++ b/packages/js/src/dashboard/constants/index.js @@ -0,0 +1,5 @@ +/** + * Keep constants centralized to avoid circular dependency problems. + */ +export const STORE_NAME = "@yoast/dashboard"; + diff --git a/packages/js/src/dashboard/initialize.js b/packages/js/src/dashboard/initialize.js new file mode 100644 index 00000000000..7045b79d2bb --- /dev/null +++ b/packages/js/src/dashboard/initialize.js @@ -0,0 +1,32 @@ +import { SlotFillProvider } from "@wordpress/components"; +import { select } from "@wordpress/data"; +import domReady from "@wordpress/dom-ready"; +import { render } from "@wordpress/element"; +import { Root } from "@yoast/ui-library"; +import { get } from "lodash"; +import { LINK_PARAMS_NAME } from "../shared-admin/store"; +import App from "./app"; +import { STORE_NAME } from "./constants"; +import registerStore from "./store"; + +domReady( () => { + const root = document.getElementById( "yoast-seo-dashboard" ); + if ( ! root ) { + return; + } + registerStore( { + initialState: { + [ LINK_PARAMS_NAME ]: get( window, "wpseoScriptData.linkParams", {} ), + }, + } ); + const isRtl = select( STORE_NAME ).selectPreference( "isRtl", false ); + + render( + + + + + , + root + ); +} ); diff --git a/packages/js/src/dashboard/store/index.js b/packages/js/src/dashboard/store/index.js new file mode 100644 index 00000000000..f16cf812f3e --- /dev/null +++ b/packages/js/src/dashboard/store/index.js @@ -0,0 +1,49 @@ +// eslint-disable-next-line import/named +import { combineReducers, createReduxStore, register } from "@wordpress/data"; +import { merge } from "lodash"; +import { getInitialLinkParamsState, LINK_PARAMS_NAME, linkParamsActions, linkParamsReducer, linkParamsSelectors } from "../../shared-admin/store"; +import { STORE_NAME } from "../constants"; +import preferences, { createInitialPreferencesState, preferencesActions, preferencesSelectors } from "./preferences"; + +/** @typedef {import("@wordpress/data/src/types").WPDataStore} WPDataStore */ + +/** + * @param {Object} initialState Initial state. + * @returns {WPDataStore} The WP data store. + */ +const createStore = ( { initialState } ) => { + return createReduxStore( STORE_NAME, { + actions: { + ...linkParamsActions, + ...preferencesActions, + }, + selectors: { + ...linkParamsSelectors, + ...preferencesSelectors, + }, + initialState: merge( + {}, + { + [ LINK_PARAMS_NAME ]: getInitialLinkParamsState(), + preferences: createInitialPreferencesState(), + }, + initialState + ), + reducer: combineReducers( { + [ LINK_PARAMS_NAME ]: linkParamsReducer, + preferences, + } ), + + } ); +}; + +/** + * Registers the store to WP data's default registry. + * @param {Object} [initialState] Initial state. + * @returns {void} + */ +const registerStore = ( { initialState = {} } = {} ) => { + register( createStore( { initialState } ) ); +}; + +export default registerStore; diff --git a/packages/js/src/dashboard/store/preferences.js b/packages/js/src/dashboard/store/preferences.js new file mode 100644 index 00000000000..08dec22f809 --- /dev/null +++ b/packages/js/src/dashboard/store/preferences.js @@ -0,0 +1,34 @@ +import { createSelector, createSlice } from "@reduxjs/toolkit"; +import { get } from "lodash"; + +/** + * @returns {Object} The initial state. + */ +export const createInitialPreferencesState = () => ( { + ...get( window, "wpseoScriptData.preferences", {} ), +} ); + +const slice = createSlice( { + name: "preferences", + initialState: createInitialPreferencesState(), + reducers: {}, +} ); + +export const preferencesSelectors = { + selectPreference: ( state, preference, defaultValue = {} ) => get( state, `preferences.${ preference }`, defaultValue ), + selectPreferences: state => get( state, "preferences", {} ), +}; +preferencesSelectors.selectUpsellSettingsAsProps = createSelector( + [ + state => preferencesSelectors.selectPreference( state, "upsellSettings", {} ), + ( state, ctbName = "premiumCtbId" ) => ctbName, + ], + ( upsellSettings, ctbName ) => ( { + "data-action": upsellSettings?.actionId, + "data-ctb-id": upsellSettings?.[ ctbName ], + } ) +); + +export const preferencesActions = slice.actions; + +export default slice.reducer; diff --git a/src/dashboard/user-interface/general-page-integration.php b/src/dashboard/user-interface/general-page-integration.php new file mode 100644 index 00000000000..4e20ed59055 --- /dev/null +++ b/src/dashboard/user-interface/general-page-integration.php @@ -0,0 +1,169 @@ +asset_manager = $asset_manager; + $this->current_page_helper = $current_page_helper; + $this->product_helper = $product_helper; + $this->shortlink_helper = $shortlink_helper; + } + + /** + * Returns the conditionals based on which this loadable should be active. + * + * @return array + */ + public static function get_conditionals() { + return [ Admin_Conditional::class ]; + } + + /** + * Initializes the integration. + * + * This is the place to register hooks and filters. + * + * @return void + */ + public function register_hooks() { + if ( \apply_filters( 'wpseo_new_dashboard', false ) ) { + // Add page. + \add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ] ); + + // Are we on the dashboard page? + if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) { + \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); + } + } + } + + /** + * Adds the page. + * + * @param array> $pages The pages. + * + * @return array> The pages. + */ + public function add_page( $pages ) { + \array_splice( + $pages, + 3, + 0, + [ + [ + 'wpseo_dashboard', + '', + \__( 'Dashboard New', 'wordpress-seo' ), + 'wpseo_manage_options', + self::PAGE, + [ $this, 'display_page' ], + ], + ] + ); + + return $pages; + } + + /** + * Displays the page. + * + * @return void + */ + public function display_page() { + echo '
'; + } + + /** + * Enqueues the assets. + * + * @return void + */ + public function enqueue_assets() { + // Remove the emoji script as it is incompatible with both React and any contenteditable fields. + \remove_action( 'admin_print_scripts', 'print_emoji_detection_script' ); + \wp_enqueue_media(); + $this->asset_manager->enqueue_script( 'new-dashboard' ); + $this->asset_manager->enqueue_style( 'new-dashboard' ); + $this->asset_manager->localize_script( 'dashboard', 'wpseoScriptData', $this->get_script_data() ); + } + + /** + * Creates the script data. + * + * @return array>> The script data. + */ + public function get_script_data() { + return [ + 'preferences' => [ + 'isPremium' => $this->product_helper->is_premium(), + 'isRtl' => \is_rtl(), + 'pluginUrl' => \plugins_url( '', \WPSEO_FILE ), + 'upsellSettings' => [ + 'actionId' => 'load-nfd-ctb', + 'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2', + ], + ], + 'linkParams' => $this->shortlink_helper->get_query_params(), + ]; + } +} diff --git a/tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php b/tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php new file mode 100644 index 00000000000..b2e0789068a --- /dev/null +++ b/tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php @@ -0,0 +1,296 @@ +stubTranslationFunctions(); + + $this->asset_manager = Mockery::mock( WPSEO_Admin_Asset_Manager::class ); + $this->current_page_helper = Mockery::mock( Current_Page_Helper::class ); + $this->product_helper = Mockery::mock( Product_Helper::class ); + $this->shortlink_helper = Mockery::mock( Short_Link_Helper::class ); + + $this->instance = new General_Page_Integration( + $this->asset_manager, + $this->current_page_helper, + $this->product_helper, + $this->shortlink_helper + ); + } + + /** + * Tests __construct method. + * + * @covers ::__construct + * + * @return void + */ + public function test_construct() { + $this->assertInstanceOf( + Academy_Integration::class, + new Academy_Integration( + $this->asset_manager, + $this->current_page_helper, + $this->product_helper, + $this->shortlink_helper + ) + ); + } + + /** + * Tests the retrieval of the conditionals. + * + * @covers ::get_conditionals + * + * @return void + */ + public function test_get_conditionals() { + $this->assertEquals( + [ + Admin_Conditional::class, + ], + General_Page_Integration::get_conditionals() + ); + } + + /** + * Provider for test_register_hooks + * + * @return array> + */ + public static function register_hooks_provider() { + return [ + 'Not on dashboard' => [ + 'current_page' => 'not dashboard', + 'action_times' => 0, + ], + 'On dashboard page' => [ + 'current_page' => 'wpseo_page_dashboard_new', + 'action_times' => 1, + ], + ]; + } + + /** + * Tests the registration of the hooks. + * + * @covers ::register_hooks + * + * @dataProvider register_hooks_provider + * + * @param string $current_page The current page. + * @param int $action_times The number of times the action should be called. + * + * @return void + */ + public function test_register_hooks_on_dashboard_page( $current_page, $action_times ) { + + Monkey\Functions\expect( 'add_filter' ) + ->with( 'wpseo_submenu_page', [ $this->instance, 'add_page' ] ) + ->once(); + + Monkey\Functions\expect( 'apply_filters' ) + ->with( 'wpseo_new_dashboard', false ) + ->once()->andReturnTrue(); + + $this->current_page_helper + ->expects( 'get_current_yoast_seo_page' ) + ->once() + ->andReturn( $current_page ); + + Monkey\Functions\expect( 'add_action' ) + ->with( 'admin_enqueue_scripts', [ $this->instance, 'enqueue_assets' ] ) + ->times( $action_times ); + + $this->instance->register_hooks(); + } + + /** + * Tests the addition of the page to the submenu. + * + * @covers ::add_page + * + * @return void + */ + public function test_add_page() { + $pages = $this->instance->add_page( + [ + [ 'page1', '', 'Page 1', 'manage_options', 'page1', [ $this, 'display_page' ] ], + [ 'page2', '', 'Page 2', 'manage_options', 'page2', [ $this, 'display_page' ] ], + [ 'page3', '', 'Page 3', 'manage_options', 'page3', [ $this, 'display_page' ] ], + ] + ); + + // Assert that the new page was added at index 3. + $this->assertEquals( 'wpseo_dashboard', $pages[3][0] ); + $this->assertEquals( '', $pages[3][1] ); + $this->assertEquals( 'Dashboard New', $pages[3][2] ); + $this->assertEquals( 'wpseo_manage_options', $pages[3][3] ); + $this->assertEquals( 'wpseo_page_dashboard_new', $pages[3][4] ); + $this->assertEquals( [ $this->instance, 'display_page' ], $pages[3][5] ); + } + + /** + * Test display_page + * + * @covers ::display_page + * + * @return void + */ + public function test_display_page() { + $this->expectOutputString( '
' ); + $this->instance->display_page(); + } + + /** + * Test enqueue_assets + * + * @covers ::enqueue_assets + * @covers ::get_script_data + * + * @return void + */ + public function test_enqueue_assets() { + Monkey\Functions\expect( 'remove_action' ) + ->with( 'admin_print_scripts', 'print_emoji_detection_script' ) + ->once(); + + Monkey\Functions\expect( 'wp_enqueue_media' )->once(); + + $this->asset_manager + ->expects( 'enqueue_script' ) + ->with( 'new-dashboard' ) + ->once(); + + $this->asset_manager + ->expects( 'enqueue_style' ) + ->with( 'new-dashboard' ) + ->once(); + + $this->asset_manager + ->expects( 'localize_script' ) + ->once(); + + $this->expect_get_script_data(); + + $this->instance->enqueue_assets(); + } + + /** + * Expectations for get_script_data. + * + * @return array> The expected data. + */ + public function expect_get_script_data() { + $link_params = [ + 'php_version' => '8.1', + 'platform' => 'wordpress', + 'platform_version' => '6.2', + 'software' => 'free', + 'software_version' => '20.6-RC2', + 'days_active' => '6-30', + 'user_language' => 'en_US', + ]; + + $this->product_helper + ->expects( 'is_premium' ) + ->once() + ->andReturn( false ); + + Monkey\Functions\expect( 'is_rtl' )->once()->andReturn( false ); + + Monkey\Functions\expect( 'plugins_url' )->once()->andReturn( 'http://basic.wordpress.test/wp-content/worspress-seo' ); + + $this->shortlink_helper + ->expects( 'get_query_params' ) + ->once() + ->andReturn( $link_params ); + + return $link_params; + } + + /** + * Test for get_script_data that is used in enqueue_assets. + * + * @covers ::get_script_data + * + * @return void + */ + public function test_get_script_data() { + $link_params = $this->expect_get_script_data(); + $expected = [ + 'preferences' => [ + 'isPremium' => false, + 'isRtl' => false, + 'pluginUrl' => 'http://basic.wordpress.test/wp-content/worspress-seo', + 'upsellSettings' => [ + 'actionId' => 'load-nfd-ctb', + 'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2', + ], + ], + 'linkParams' => $link_params, + ]; + + $this->assertSame( $expected, $this->instance->get_script_data() ); + } +} From ffd6607316f7fb7442808d3f2fe33d41084e723d Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Thu, 5 Sep 2024 16:21:19 +0200 Subject: [PATCH 058/313] Transforms languageProcessing helpers to use class syntax --- .../helpers/morphology/buildTopicStems.js | 51 ++--- .../helpers/syllables/DeviationFragment.js | 181 +++++++++--------- .../syllables/syllableCountIterator.js | 89 ++++----- .../helpers/syllables/syllableCountStep.js | 109 ++++++----- 4 files changed, 222 insertions(+), 208 deletions(-) diff --git a/packages/yoastseo/src/languageProcessing/helpers/morphology/buildTopicStems.js b/packages/yoastseo/src/languageProcessing/helpers/morphology/buildTopicStems.js index cf846eb1e8c..72387354617 100644 --- a/packages/yoastseo/src/languageProcessing/helpers/morphology/buildTopicStems.js +++ b/packages/yoastseo/src/languageProcessing/helpers/morphology/buildTopicStems.js @@ -6,31 +6,36 @@ import { isUndefined, escapeRegExp, memoize } from "lodash"; import isDoubleQuoted from "../match/isDoubleQuoted"; /** - * A topic phrase (i.e., a keyphrase or synonym) with stem-original pairs for the words in the topic phrase. - * - * @param {StemOriginalPair[]} stemOriginalPairs The stem-original pairs for the words in the topic phrase. - * @param {boolean} exactMatch Whether the topic phrase is an exact match. - * - * @constructor - */ -function TopicPhrase( stemOriginalPairs = [], exactMatch = false ) { - this.stemOriginalPairs = stemOriginalPairs; - this.exactMatch = exactMatch; -} - -/** - * Returns all stems in the topic phrase. - * - * @returns {string[]|[]} The stems in the topic phrase or empty array if the topic phrase is exact match. + * A TopicPhrase (i.e., a keyphrase or synonym) with stem-original pairs for the words in the topic phrase. */ -TopicPhrase.prototype.getStems = function() { - // An exact match keyphrase doesn't have stems. - if ( this.exactMatch ) { - return []; +class TopicPhrase { + /** + * Constructs a new TopicPhrase. + * + * @param {StemOriginalPair[]} stemOriginalPairs The stem-original pairs for the words in the topic phrase. + * @param {boolean} exactMatch Whether the topic phrase is an exact match. + * + * @constructor + */ + constructor( stemOriginalPairs = [], exactMatch = false ) { + this.stemOriginalPairs = stemOriginalPairs; + this.exactMatch = exactMatch; } - return this.stemOriginalPairs.map( stemOriginalPair => stemOriginalPair.stem ); -}; + /** + * Returns all stems in the topic phrase. + * + * @returns {string[]|[]} The stems in the topic phrase or empty array if the topic phrase is exact match. + */ + getStems() { + // An exact match keyphrase doesn't have stems. + if ( this.exactMatch ) { + return []; + } + + return this.stemOriginalPairs.map( stemOriginalPair => stemOriginalPair.stem ); + } +} /** * A stem-original pair ƒor a word in a topic phrase. @@ -64,7 +69,7 @@ const buildStems = function( keyphrase, stemmer, functionWords, areHyphensWordBo return new TopicPhrase(); } - // If the keyphrase is embedded in double quotation marks, return the keyprhase itself, without the outermost quotation marks. + // If the keyphrase is embedded in double quotation marks, return the keyphrase itself, without the outermost quotation marks. if ( isDoubleQuoted( keyphrase ) ) { keyphrase = keyphrase.substring( 1, keyphrase.length - 1 ); return new TopicPhrase( diff --git a/packages/yoastseo/src/languageProcessing/helpers/syllables/DeviationFragment.js b/packages/yoastseo/src/languageProcessing/helpers/syllables/DeviationFragment.js index c7f77ae6165..1b8b9f24422 100644 --- a/packages/yoastseo/src/languageProcessing/helpers/syllables/DeviationFragment.js +++ b/packages/yoastseo/src/languageProcessing/helpers/syllables/DeviationFragment.js @@ -1,109 +1,112 @@ import { isUndefined, pick } from "lodash"; /** - * Represents a partial deviation when counting syllables - * - * @param {Object} options Extra options about how to match this fragment. - * @param {string} options.location The location in the word where this deviation can occur. - * @param {string} options.word The actual string that should be counted differently. - * @param {number} options.syllables The amount of syllables this fragment has. - * @param {string[]} [options.notFollowedBy] A list of characters that this fragment shouldn't be followed with. - * @param {string[]} [options.alsoFollowedBy] A list of characters that this fragment could be followed with. - * - * @constructor + * A DeviationFragment represents a partial deviation when counting syllables. */ -function DeviationFragment( options ) { - this._location = options.location; - this._fragment = options.word; - this._syllables = options.syllables; - this._regex = null; +export default class DeviationFragment { + /** + * Constructs a new DeviationFragment. + * + * @param {Object} options Extra options about how to match this fragment. + * @param {string} options.location The location in the word where this deviation can occur. + * @param {string} options.word The actual string that should be counted differently. + * @param {number} options.syllables The amount of syllables this fragment has. + * @param {string[]} [options.notFollowedBy] A list of characters that this fragment shouldn't be followed with. + * @param {string[]} [options.alsoFollowedBy] A list of characters that this fragment could be followed with. + * + * @constructor + */ + constructor( options ) { + this._location = options.location; + this._fragment = options.word; + this._syllables = options.syllables; + this._regex = null; + + this._options = pick( options, [ "notFollowedBy", "alsoFollowedBy" ] ); + } - this._options = pick( options, [ "notFollowedBy", "alsoFollowedBy" ] ); -} + /** + * Creates a regex that matches this fragment inside a word. + * + * @returns {void} + */ + createRegex() { + const options = this._options; -/** - * Creates a regex that matches this fragment inside a word. - * - * @returns {void} - */ -DeviationFragment.prototype.createRegex = function() { - let regexString = ""; - const options = this._options; + let fragment = this._fragment; - let fragment = this._fragment; + if ( ! isUndefined( options.notFollowedBy ) ) { + fragment += "(?![" + options.notFollowedBy.join( "" ) + "])"; + } - if ( ! isUndefined( options.notFollowedBy ) ) { - fragment += "(?![" + options.notFollowedBy.join( "" ) + "])"; - } + if ( ! isUndefined( options.alsoFollowedBy ) ) { + fragment += "[" + options.alsoFollowedBy.join( "" ) + "]?"; + } - if ( ! isUndefined( options.alsoFollowedBy ) ) { - fragment += "[" + options.alsoFollowedBy.join( "" ) + "]?"; - } + let regexString; + switch ( this._location ) { + case "atBeginning": + regexString = "^" + fragment; + break; - switch ( this._location ) { - case "atBeginning": - regexString = "^" + fragment; - break; + case "atEnd": + regexString = fragment + "$"; + break; - case "atEnd": - regexString = fragment + "$"; - break; + case "atBeginningOrEnd": + regexString = "(^" + fragment + ")|(" + fragment + "$)"; + break; - case "atBeginningOrEnd": - regexString = "(^" + fragment + ")|(" + fragment + "$)"; - break; + default: + regexString = fragment; + break; + } - default: - regexString = fragment; - break; + this._regex = new RegExp( regexString ); } - this._regex = new RegExp( regexString ); -}; - -/** - * Returns the regex that matches this fragment inside a word. - * - * @returns {RegExp} The regexp that matches this fragment. - */ -DeviationFragment.prototype.getRegex = function() { - if ( null === this._regex ) { - this.createRegex(); + /** + * Returns the regex that matches this fragment inside a word. + * + * @returns {RegExp} The regex that matches this fragment. + */ + getRegex() { + if ( null === this._regex ) { + this.createRegex(); + } + + return this._regex; } - return this._regex; -}; + /** + * Returns whether this fragment occurs in a word. + * + * @param {string} word The word to match the fragment in. + * @returns {boolean} Whether or not this fragment occurs in a word. + */ + occursIn( word ) { + const regex = this.getRegex(); -/** - * Returns whether or not this fragment occurs in a word. - * - * @param {string} word The word to match the fragment in. - * @returns {boolean} Whether or not this fragment occurs in a word. - */ -DeviationFragment.prototype.occursIn = function( word ) { - const regex = this.getRegex(); - - return regex.test( word ); -}; - -/** - * Removes this fragment from the given word. - * - * @param {string} word The word to remove this fragment from. - * @returns {string} The modified word. - */ -DeviationFragment.prototype.removeFrom = function( word ) { - // Replace by a space to keep the remaining parts separated. - return word.replace( this._fragment, " " ); -}; + return regex.test( word ); + } -/** - * Returns the amount of syllables for this fragment. - * - * @returns {number} The amount of syllables for this fragment. - */ -DeviationFragment.prototype.getSyllables = function() { - return this._syllables; -}; + /** + * Removes this fragment from the given word. + * + * @param {string} word The word to remove this fragment from. + * @returns {string} The modified word. + */ + removeFrom( word ) { + // Replace by a space to keep the remaining parts separated. + return word.replace( this._fragment, " " ); + } -export default DeviationFragment; + /** + * Returns the amount of syllables for this fragment. + * + * @returns {number} The amount of syllables for this fragment. + */ + getSyllables() { + return this._syllables; + } +} diff --git a/packages/yoastseo/src/languageProcessing/helpers/syllables/syllableCountIterator.js b/packages/yoastseo/src/languageProcessing/helpers/syllables/syllableCountIterator.js index 7a8853aaecf..143045fdcb2 100644 --- a/packages/yoastseo/src/languageProcessing/helpers/syllables/syllableCountIterator.js +++ b/packages/yoastseo/src/languageProcessing/helpers/syllables/syllableCountIterator.js @@ -3,51 +3,54 @@ import SyllableCountStep from "./syllableCountStep.js"; import { forEach, isUndefined } from "lodash"; /** - * Creates a syllable count iterator. - * - * @param {object} config The config object containing an array with syllable exclusions. - * @constructor + * A SyllableCountIterator contains individual SyllableCountSteps. */ -const SyllableCountIterator = function( config ) { - this.countSteps = []; - if ( ! isUndefined( config ) ) { - this.createSyllableCountSteps( config.deviations.vowels ); +export default class SyllableCountIterator { + /** + * Creates a syllable count iterator. + * + * @param {object} config The config object containing an array with syllable exclusions. + * @constructor + */ + constructor( config ) { + this.countSteps = []; + if ( ! isUndefined( config ) ) { + this.createSyllableCountSteps( config.deviations.vowels ); + } } -}; -/** - * Creates a syllable count step object for each exclusion. - * - * @param {object} syllableCounts The object containing all exclusion syllables including the multipliers. - * @returns {void} - */ -SyllableCountIterator.prototype.createSyllableCountSteps = function( syllableCounts ) { - forEach( syllableCounts, function( syllableCountStep ) { - this.countSteps.push( new SyllableCountStep( syllableCountStep ) ); - }.bind( this ) ); -}; - -/** - * Returns all available count steps. - * - * @returns {Array} All available count steps. - */ -SyllableCountIterator.prototype.getAvailableSyllableCountSteps = function() { - return this.countSteps; -}; + /** + * Creates a syllable count step object for each exclusion. + * + * @param {object} syllableCounts The object containing all exclusion syllables including the multipliers. + * @returns {void} + */ + createSyllableCountSteps( syllableCounts ) { + forEach( syllableCounts, function( syllableCountStep ) { + this.countSteps.push( new SyllableCountStep( syllableCountStep ) ); + }.bind( this ) ); + } -/** - * Counts the syllables for all the steps and returns the total syllable count. - * - * @param {String} word The word to count syllables in. - * @returns {number} The number of syllables found based on exclusions. - */ -SyllableCountIterator.prototype.countSyllables = function( word ) { - let syllableCount = 0; - forEach( this.countSteps, function( step ) { - syllableCount += step.countSyllables( word ); - } ); - return syllableCount; -}; + /** + * Returns all available count steps. + * + * @returns {Array} All available count steps. + */ + getAvailableSyllableCountSteps() { + return this.countSteps; + } -export default SyllableCountIterator; + /** + * Counts the syllables for all the steps and returns the total syllable count. + * + * @param {String} word The word to count syllables in. + * @returns {number} The number of syllables found based on exclusions. + */ + countSyllables( word ) { + let syllableCount = 0; + forEach( this.countSteps, function( step ) { + syllableCount += step.countSyllables( word ); + } ); + return syllableCount; + } +} diff --git a/packages/yoastseo/src/languageProcessing/helpers/syllables/syllableCountStep.js b/packages/yoastseo/src/languageProcessing/helpers/syllables/syllableCountStep.js index e6a7197a94b..c9c11cdabc4 100644 --- a/packages/yoastseo/src/languageProcessing/helpers/syllables/syllableCountStep.js +++ b/packages/yoastseo/src/languageProcessing/helpers/syllables/syllableCountStep.js @@ -3,63 +3,66 @@ import { isUndefined } from "lodash"; import arrayToRegex from "../regex/createRegexFromArray.js"; /** - * Constructs a language syllable regex that contains a regex for matching syllable exclusion. - * - * @param {object} syllableRegex The object containing the syllable exclusions. - * @constructor + * A SyllableCountStep is an individual step in a SyllableCountIterator. */ -const SyllableCountStep = function( syllableRegex ) { - this._hasRegex = false; - this._regex = ""; - this._multiplier = ""; - this.createRegex( syllableRegex ); -}; - -/** - * Returns if a valid regex has been set. - * - * @returns {boolean} True if a regex has been set, false if not. - */ -SyllableCountStep.prototype.hasRegex = function() { - return this._hasRegex; -}; +export default class SyllableCountStep { + /** + * Constructs a language syllable regex that contains a regex for matching syllable exclusion. + * + * @param {object} syllableRegex The object containing the syllable exclusions. + * @constructor + */ + constructor( syllableRegex ) { + this._hasRegex = false; + this._regex = ""; + this._multiplier = ""; + this.createRegex( syllableRegex ); + } -/** - * Creates a regex based on the given syllable exclusions, and sets the multiplier to use. - * - * @param {object} syllableRegex The object containing the syllable exclusions and multiplier. - * @returns {void} - */ -SyllableCountStep.prototype.createRegex = function( syllableRegex ) { - if ( ! isUndefined( syllableRegex ) && ! isUndefined( syllableRegex.fragments ) ) { - this._hasRegex = true; - this._regex = arrayToRegex( syllableRegex.fragments, true ); - this._multiplier = syllableRegex.countModifier; + /** + * Returns if a valid regex has been set. + * + * @returns {boolean} True if a regex has been set, false if not. + */ + hasRegex() { + return this._hasRegex; } -}; -/** - * Returns the stored regular expression. - * - * @returns {RegExp} The stored regular expression. - */ -SyllableCountStep.prototype.getRegex = function() { - return this._regex; -}; + /** + * Creates a regex based on the given syllable exclusions, and sets the multiplier to use. + * + * @param {object} syllableRegex The object containing the syllable exclusions and multiplier. + * @returns {void} + */ + createRegex( syllableRegex ) { + if ( ! isUndefined( syllableRegex ) && ! isUndefined( syllableRegex.fragments ) ) { + this._hasRegex = true; + this._regex = arrayToRegex( syllableRegex.fragments, true ); + this._multiplier = syllableRegex.countModifier; + } + } -/** - * Matches syllable exclusions in a given word and the returns the number found multiplied with the - * given multiplier. - * - * @param {String} word The word to match for syllable exclusions. - * @returns {number} The amount of syllables found. - */ -SyllableCountStep.prototype.countSyllables = function( word ) { - if ( this._hasRegex ) { - const match = word.match( this._regex ) || []; - return match.length * this._multiplier; + /** + * Returns the stored regular expression. + * + * @returns {RegExp} The stored regular expression. + */ + getRegex() { + return this._regex; } - return 0; -}; -export default SyllableCountStep; + /** + * Matches syllable exclusions in a given word and the returns the number found multiplied with the + * given multiplier. + * + * @param {String} word The word to match for syllable exclusions. + * @returns {number} The amount of syllables found. + */ + countSyllables( word ) { + if ( this._hasRegex ) { + const match = word.match( this._regex ) || []; + return match.length * this._multiplier; + } + return 0; + } +} From 6b21968dc3f3d4ef67b2363904121e7f2236cd6c Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Thu, 5 Sep 2024 16:21:42 +0200 Subject: [PATCH 059/313] Transforms the factory helper to use class syntax --- packages/yoastseo/src/helpers/factory.js | 357 +++++++++++------------ 1 file changed, 174 insertions(+), 183 deletions(-) diff --git a/packages/yoastseo/src/helpers/factory.js b/packages/yoastseo/src/helpers/factory.js index af1439897b1..1127de7fd93 100644 --- a/packages/yoastseo/src/helpers/factory.js +++ b/packages/yoastseo/src/helpers/factory.js @@ -1,73 +1,152 @@ -/** - * A mock factory function. - * - * @returns {void} - */ import { isUndefined } from "lodash"; /** - * Factory prototype. + * FactoryProto is a mock factory function. * - * @constructor - */ -const FactoryProto = function() {}; - -/** - * Returns a mock element that lodash accepts as an element - * - * @returns {object} Mock HTML element. + * @returns {void} */ -FactoryProto.prototype.buildMockElement = function() { - const mockElement = []; - mockElement.nodeType = 1; - - return mockElement; -}; +export default class FactoryProto { + /** + * Returns a mock element that lodash accepts as an element. + * + * @returns {object} Mock HTML element. + */ + static buildMockElement() { + const mockElement = []; + mockElement.nodeType = 1; + + return mockElement; + } -/** - * Returns a mock researcher - * - * @param {object} expectedValue The expected value or values. - * @param {boolean} multiValue True if multiple values are expected. - * @param {boolean} hasMorphologyData True if the researcher has access to morphology data. - * @param {Object|boolean} config Optional config to be used for an assessment. - * @param {Object|boolean} helpers Optional helpers to be used for an assessment. - * - * @returns {Researcher} Mock researcher. - */ -FactoryProto.prototype.buildMockResearcher = function( expectedValue, multiValue = false, hasMorphologyData = false, - config = false, helpers = false ) { - if ( multiValue && ( typeof expectedValue === "object" || typeof helpers === "object" || typeof config === "object" ) ) { + /** + * Returns a mock researcher + * + * @param {object} expectedValue The expected value or values. + * @param {boolean} multiValue True if multiple values are expected. + * @param {boolean} hasMorphologyData True if the researcher has access to morphology data. + * @param {Object|boolean} config Optional config to be used for an assessment. + * @param {Object|boolean} helpers Optional helpers to be used for an assessment. + * + * @returns {Researcher} Mock researcher. + */ + static buildMockResearcher( expectedValue, multiValue = false, hasMorphologyData = false, + config = false, helpers = false ) { + if ( multiValue && ( typeof expectedValue === "object" || typeof helpers === "object" || typeof config === "object" ) ) { + return { + /** + * Return research results by research name for multi-value mock researchers. + * + * @param {string} research The name of the research. + * + * @returns {Object} The results of the research. + */ + getResearch: function( research ) { + return expectedValue[ research ]; + }, + + /** + * Return whether the worker has the research. + * @param {string} research The name of the research. + * @returns {boolean} Whether the worker has the research. + */ + hasResearch: function( research ) { + return ! isUndefined( expectedValue[ research ] ); + }, + + /** + * Adds a research. + * @param {string} name The name of the research. + * @param {Object} research The research to register. + * + * @returns {void} + */ + addResearch: function( name, research ) { + expectedValue[ name ] = research; + }, + + /** + * Check whether morphology data is available. + * + * @returns {boolean} True if the researcher has access to morphology data. + */ + getData: function() { + return hasMorphologyData; + }, + + /** + * Return the helper to be used for the assessment. + * @param {string} name The name of the helper. + * + * @returns {function} The helper for the assessment. + */ + getHelper: function( name ) { + return helpers[ name ]; + }, + + /** + * Checks whether a helper with the given name exists. + * @param {string} name The name to check. + * + * @returns {boolean} True if the helper exists. + */ + hasHelper: function( name ) { + return ! isUndefined( helpers[ name ] ); + }, + + /** + * Adds a helper under the given name. + * @param {string} name The name. + * @param {function} helper The helper. + * + * @returns {void} + */ + addHelper: function( name, helper ) { + if ( ! helpers ) { + helpers = {}; + } + helpers[ name ] = helper; + }, + + /** + * Return the config to be used for the assessment. + * @param {string} name The name of the config. + * + * @returns {function} The config for the assessment. + */ + getConfig: function( name ) { + return config[ name ]; + }, + + /** + * Checks if the config exists. + * @param {string} name The name of the config + * + * @returns {boolean} Whether the config exists. + */ + hasConfig: function( name ) { + return ! isUndefined( config[ name ] ); + }, + + /** + * Adds a configuration. + * @param {string} name The name of the config. + * @param {Object} researchConfig The config. + * + * @returns {void} + */ + addConfig: function( name, researchConfig ) { + config[ name ] = researchConfig; + }, + }; + } return { /** - * Return research results by research name for multi-value mock researchers. - * - * @param {string} research The name of the research. + * Return research results. * * @returns {Object} The results of the research. */ - getResearch: function( research ) { - return expectedValue[ research ]; - }, - - /** - * Return whether the worker has the research. - * @param {string} research The name of the research. - * @returns {boolean} Whether the worker has the research. - */ - hasResearch: function( research ) { - return ! isUndefined( expectedValue[ research ] ); - }, - - /** - * Adds a research. - * @param {string} name The name of the research. - * @param {Object} research The research to register. - * - * @returns {void} - */ - addResearch: function( name, research ) { - expectedValue[ name ] = research; + getResearch: function() { + return expectedValue; }, /** @@ -80,149 +159,61 @@ FactoryProto.prototype.buildMockResearcher = function( expectedValue, multiValue }, /** - * Return the helper to be used for the assessment. - * @param {string} name The name of the helper. - * - * @returns {function} The helper for the assessment. - */ - getHelper: function( name ) { - return helpers[ name ]; - }, - - /** - * Checks whether a helper with the given name exists. - * @param {string} name The name to check. + * Return the helpers to be used for the assessment. * - * @returns {boolean} Trye if the helper exists. + * @returns {Object} The helpers for the assessment. */ - hasHelper: function( name ) { - return ! isUndefined( helpers[ name ] ); + getHelper: function() { + return helpers; }, /** - * Adds a helper under the given name. - * @param {string} name The name. - * @param {function} helper The helper. + * Return whether the worker has the helper. * - * @returns {void} + * @returns {boolean} Whether the worker has the helper. */ - addHelper: function( name, helper ) { - if ( ! helpers ) { - helpers = {}; - } - helpers[ name ] = helper; + hasHelper: function() { + return expectedValue; }, /** * Return the config to be used for the assessment. - * @param {string} name The name of the config. - * - * @returns {function} The config for the assessment. - */ - getConfig: function( name ) { - return config[ name ]; - }, - - /** - * Checks if the config exists. - * @param {string} name The name of the config * - * @returns {boolean} Whether the config exists. + * @returns {Object} The config for the assessment results. */ - hasConfig: function( name ) { - return ! isUndefined( config[ name ] ); + getConfig: function() { + return config; }, /** - * Adds a configuration. - * @param {string} name The name of the config. - * @param {Object} researchConfig The config. - * - * @returns {void} + * Return whether the worker has the config. + * @param {string} research The name of the config. + * @returns {boolean} Whether the worker has the research. */ - addConfig: function( name, researchConfig ) { - config[ name ] = researchConfig; + hasConfig: function( research ) { + return ! isUndefined( expectedValue[ research ] ); }, }; } - return { - /** - * Return research results. - * - * @returns {Object} The results of the research. - */ - getResearch: function() { - return expectedValue; - }, - - /** - * Check whether morphology data is available. - * - * @returns {boolean} True if the researcher has access to morphology data. - */ - getData: function() { - return hasMorphologyData; - }, - - /** - * Return the helpers to be used for the assessment. - * - * @returns {Object} The helpers for the assessment. - */ - getHelper: function() { - return helpers; - }, - - /** - * Return whether the worker has the helper. - * - * @returns {boolean} Whether the worker has the helper. - */ - hasHelper: function() { - return expectedValue; - }, - - /** - * Return the config to be used for the assessment. - * - * @returns {Object} The config for the assessment results. - */ - getConfig: function() { - return config; - }, - - /** - * Return whether the worker has the config. - * @param {string} research The name of the config. - * @returns {boolean} Whether the worker has the research. - */ - hasConfig: function( research ) { - return ! isUndefined( expectedValue[ research ] ); - }, - }; -}; - -/** - * This method repeats a string and returns a new string based on the string and the amount of repetitions. - * - * @param {string} string String to repeat. - * @param {int} repetitions Number of repetitions. - * - * @returns {string} The result. - */ -FactoryProto.prototype.buildMockString = function( string, repetitions ) { - let resultString = ""; - - string = string || "Test "; - repetitions = repetitions || 1; - for ( let i = 0; i < repetitions; i++ ) { - resultString += string; + /** + * This method repeats a string and returns a new string based on the string and the amount of repetitions. + * + * @param {string} string String to repeat. + * @param {int} repetitions Number of repetitions. + * + * @returns {string} The result. + */ + static buildMockString( string, repetitions ) { + let resultString = ""; + + string = string || "Test "; + repetitions = repetitions || 1; + + for ( let i = 0; i < repetitions; i++ ) { + resultString += string; + } + + return resultString; } - - return resultString; -}; - -const Factory = new FactoryProto(); - -export default Factory; +} From c8e1ee3b0b5ae5bb2b529b9452daa2fe0f6ffbe6 Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Thu, 5 Sep 2024 16:22:41 +0200 Subject: [PATCH 060/313] Transforms the previouslyUsedKeywords plugin to use class syntax --- .../bundledPlugins/previouslyUsedKeywords.js | 331 +++++++++--------- 1 file changed, 168 insertions(+), 163 deletions(-) diff --git a/packages/yoastseo/src/bundledPlugins/previouslyUsedKeywords.js b/packages/yoastseo/src/bundledPlugins/previouslyUsedKeywords.js index d74a2e594d0..7c61e17f4c1 100644 --- a/packages/yoastseo/src/bundledPlugins/previouslyUsedKeywords.js +++ b/packages/yoastseo/src/bundledPlugins/previouslyUsedKeywords.js @@ -2,186 +2,191 @@ import { __, sprintf } from "@wordpress/i18n"; import { isUndefined } from "lodash"; import MissingArgument from "../errors/missingArgument"; -import { createAnchorOpeningTag } from "../helpers/shortlinker"; +import { createAnchorOpeningTag } from "../helpers"; import AssessmentResult from "../values/AssessmentResult.js"; - /** - * @param {object} app The app - * @param {object} args An arguments object with usedKeywords, searchUrl, postUrl, - * @param {object} args.usedKeywords An object with keywords and ids where they are used. - * @param {object} args.usedKeywordsPostTypes An object with the post types of the post ids from usedKeywords. - * @param {string} args.searchUrl The url used to link to a search page when multiple usages of the keyword are found. - * @param {string} args.postUrl The url used to link to a post when 1 usage of the keyword is found. - * @constructor + * The PreviouslyUsedKeyword plugin allows to check for previously used keywords. */ -var PreviouslyUsedKeyword = function( app, args ) { - if ( isUndefined( app ) ) { - throw new MissingArgument( "The previously keyword plugin requires the YoastSEO app" ); - } +export default class PreviouslyUsedKeyword { + /** + * Constructs a new PreviouslyUsedKeyword plugin. + * + * @param {object} app The app. + * @param {object} args An arguments object. + * @param {object} args.usedKeywords An object with keywords and ids where they are used. + * @param {object} args.usedKeywordsPostTypes An object with the post types of the post ids from usedKeywords. + * @param {string} args.searchUrl The url used to link to a search page when multiple usages of the keyword are found. + * @param {string} args.postUrl The url used to link to a post when 1 usage of the keyword is found. + * + * @constructor + */ + constructor( app, args ) { + if ( isUndefined( app ) ) { + throw new MissingArgument( "The previously keyword plugin requires the YoastSEO app" ); + } - if ( isUndefined( args ) ) { - args = { - usedKeywords: {}, - usedKeywordsPostTypes: {}, - searchUrl: "", - postUrl: "", - }; - } + if ( isUndefined( args ) ) { + args = { + usedKeywords: {}, + usedKeywordsPostTypes: {}, + searchUrl: "", + postUrl: "", + }; + } - this.app = app; - this.usedKeywords = args.usedKeywords; - this.usedKeywordsPostTypes = args.usedKeywordsPostTypes; - this.searchUrl = args.searchUrl; - this.postUrl = args.postUrl; - this.urlTitle = createAnchorOpeningTag( "https://yoa.st/33x" ); - this.urlCallToAction = createAnchorOpeningTag( "https://yoa.st/33y" ); -}; + this.app = app; + this.usedKeywords = args.usedKeywords; + this.usedKeywordsPostTypes = args.usedKeywordsPostTypes; + this.searchUrl = args.searchUrl; + this.postUrl = args.postUrl; + this.urlTitle = createAnchorOpeningTag( "https://yoa.st/33x" ); + this.urlCallToAction = createAnchorOpeningTag( "https://yoa.st/33y" ); + } -/** - * Registers the assessment with the assessor. - * - * @returns {void} - */ -PreviouslyUsedKeyword.prototype.registerPlugin = function() { - this.app.registerAssessment( "usedKeywords", { - getResult: this.assess.bind( this ), - /** - * Checks if the paper has a keyphrase, which is a prerequisite for the assessment to run. - * - * @param {Paper} paper The paper. - * - * @returns {boolean} Whether the paper has a keyphrase. - */ - isApplicable: function( paper ) { - return paper.hasKeyword(); - }, - }, "previouslyUsedKeywords" ); -}; + /** + * Registers the assessment with the assessor. + * + * @returns {void} + */ + registerPlugin() { + this.app.registerAssessment( "usedKeywords", { + getResult: this.assess.bind( this ), + /** + * Checks if the paper has a keyphrase, which is a prerequisite for the assessment to run. + * + * @param {Paper} paper The paper. + * + * @returns {boolean} Whether the paper has a keyphrase. + */ + isApplicable: function( paper ) { + return paper.hasKeyword(); + }, + }, "previouslyUsedKeywords" ); + } -/** - * Updates the usedKeywords. - * - * @param {object} usedKeywords An object with keywords and ids where they are used. - * @param {object} usedKeywordsPostTypes An object with keywords and in which post types they are used. - * The post types correspond with the ids in the usedKeywords parameter. - * @returns {void} - */ -PreviouslyUsedKeyword.prototype.updateKeywordUsage = function( usedKeywords, usedKeywordsPostTypes ) { - this.usedKeywords = usedKeywords; - this.usedKeywordsPostTypes = usedKeywordsPostTypes; -}; + /** + * Updates the usedKeywords. + * + * @param {object} usedKeywords An object with keywords and ids where they are used. + * @param {object} usedKeywordsPostTypes An object with keywords and in which post types they are used. + * The post types correspond with the ids in the usedKeywords parameter. + * @returns {void} + */ + updateKeywordUsage( usedKeywords, usedKeywordsPostTypes ) { + this.usedKeywords = usedKeywords; + this.usedKeywordsPostTypes = usedKeywordsPostTypes; + } -/** - * Scores the previously used keyword assessment based on the count. - * - * @param {object} previouslyUsedKeywords The result of the previously used keywords research - * @param {Paper} paper The paper object to research. - * @returns {object} the scoreobject with text and score. - */ -PreviouslyUsedKeyword.prototype.scoreAssessment = function( previouslyUsedKeywords, paper ) { - const count = previouslyUsedKeywords.count; - const id = previouslyUsedKeywords.id; - const postTypeToDisplay = previouslyUsedKeywords.postTypeToDisplay; - let url; + /** + * Scores the previously used keyword assessment based on the count. + * + * @param {object} previouslyUsedKeywords The result of the previously used keywords research + * @param {Paper} paper The paper object to research. + * @returns {{text: string, score: number}} The result object with a feedback text and a score. + */ + scoreAssessment( previouslyUsedKeywords, paper ) { + const count = previouslyUsedKeywords.count; + const id = previouslyUsedKeywords.id; + const postTypeToDisplay = previouslyUsedKeywords.postTypeToDisplay; + let url; + + if ( count === 0 ) { + return { + text: sprintf( + /* translators: + %1$s expands to a link to an article on yoast.com, + %2$s expands to an anchor end tag. */ + __( "%1$sPreviously used keyphrase%2$s: You've not used this keyphrase before, very good.", "wordpress-seo" ), + this.urlTitle, + "" + ), + score: 9, + }; + } + if ( count === 1 ) { + url = ``; + return { + /* translators: %1$s expands to an admin link where the keyphrase is already used, + %2$s expands to the anchor end tag, %3$s and %4$s expand to links on yoast.com. */ + text: sprintf( __( + "%3$sPreviously used keyphrase%2$s: You've used this keyphrase %1$sonce before%2$s. %4$sDo not use your keyphrase more than once%2$s.", + "wordpress-seo" + ), + url, + "", + this.urlTitle, + this.urlCallToAction + ), + score: 6, + }; + } - if ( count === 0 ) { - return { - text: sprintf( - /* translators: - %1$s expands to a link to an article on yoast.com, - %2$s expands to an anchor end tag. */ - __( "%1$sPreviously used keyphrase%2$s: You've not used this keyphrase before, very good.", "wordpress-seo" ), + if ( count > 1 ) { + if ( postTypeToDisplay ) { + url = ``; + } else { + url = ``; + } + + return { + /* translators: %1$s expands to a link to the admin search page for the keyphrase, + %2$s expands to the anchor end tag, %3$s and %4$s expand to links to yoast.com */ + text: sprintf( __( + "%3$sPreviously used keyphrase%2$s: You've used this keyphrase %1$smultiple times before%2$s. %4$sDo not use your keyphrase more than once%2$s.", + "wordpress-seo" + ), + url, + "", this.urlTitle, - "" - ), - score: 9, - }; - } - if ( count === 1 ) { - url = ``; - return { - /* translators: %1$s expands to an admin link where the keyphrase is already used, - %2$s expands to the anchor end tag, %3$s and %4$s expand to links on yoast.com. */ - text: sprintf( __( - "%3$sPreviously used keyphrase%2$s: You've used this keyphrase %1$sonce before%2$s. %4$sDo not use your keyphrase more than once%2$s.", - "wordpress-seo" - ), - url, - "", - this.urlTitle, - this.urlCallToAction - ), - score: 6, - }; + this.urlCallToAction + ), + score: 1, + }; + } } - if ( count > 1 ) { - if ( postTypeToDisplay ) { - url = ``; - } else { - url = ``; + /** + * Researches the previously used keywords, based on the used keywords and the keyword in the paper. + * + * @param {Paper} paper The paper object to research. + * @returns {{id: number, count: number}} The object with the count and the id of the previously used keyword + */ + researchPreviouslyUsedKeywords( paper ) { + const keyword = paper.getKeyword(); + let count = 0; + let postTypeToDisplay = ""; + let id = 0; + + if ( ! isUndefined( this.usedKeywords[ keyword ] ) && this.usedKeywords[ keyword ].length > 0 ) { + count = this.usedKeywords[ keyword ].length; + if ( keyword in this.usedKeywordsPostTypes ) { + postTypeToDisplay = this.usedKeywordsPostTypes[ keyword ][ 0 ]; + } + id = this.usedKeywords[ keyword ][ 0 ]; } return { - /* translators: %1$s expands to a link to the admin search page for the keyphrase, - %2$s expands to the anchor end tag, %3$s and %4$s expand to links to yoast.com */ - text: sprintf( __( - "%3$sPreviously used keyphrase%2$s: You've used this keyphrase %1$smultiple times before%2$s. %4$sDo not use your keyphrase more than once%2$s.", - "wordpress-seo" - ), - url, - "", - this.urlTitle, - this.urlCallToAction - ), - score: 1, + id: id, + count: count, + postTypeToDisplay: postTypeToDisplay, }; } -}; -/** - * Researches the previously used keywords, based on the used keywords and the keyword in the paper. - * - * @param {Paper} paper The paper object to research. - * @returns {{id: number, count: number}} The object with the count and the id of the previously used keyword - */ -PreviouslyUsedKeyword.prototype.researchPreviouslyUsedKeywords = function( paper ) { - const keyword = paper.getKeyword(); - let count = 0; - let postTypeToDisplay = ""; - let id = 0; - - if ( ! isUndefined( this.usedKeywords[ keyword ] ) && this.usedKeywords[ keyword ].length > 0 ) { - count = this.usedKeywords[ keyword ].length; - if ( keyword in this.usedKeywordsPostTypes ) { - postTypeToDisplay = this.usedKeywordsPostTypes[ keyword ][ 0 ]; - } - id = this.usedKeywords[ keyword ][ 0 ]; + /** + * The assessment for the previously used keywords. + * + * @param {Paper} paper The Paper object to assess. + * @returns {AssessmentResult} The assessment result of the assessment + */ + assess( paper ) { + const previouslyUsedKeywords = this.researchPreviouslyUsedKeywords( paper ); + const previouslyUsedKeywordsResult = this.scoreAssessment( previouslyUsedKeywords, paper ); + + const assessmentResult = new AssessmentResult(); + assessmentResult.setScore( previouslyUsedKeywordsResult.score ); + assessmentResult.setText( previouslyUsedKeywordsResult.text ); + + return assessmentResult; } - - return { - id: id, - count: count, - postTypeToDisplay: postTypeToDisplay, - }; -}; - -/** - * The assessment for the previously used keywords. - * - * @param {Paper} paper The Paper object to assess. - * @returns {AssessmentResult} The assessment result of the assessment - */ -PreviouslyUsedKeyword.prototype.assess = function( paper ) { - var previouslyUsedKeywords = this.researchPreviouslyUsedKeywords( paper ); - var previouslyUsedKeywordsResult = this.scoreAssessment( previouslyUsedKeywords, paper ); - - var assessmentResult = new AssessmentResult(); - assessmentResult.setScore( previouslyUsedKeywordsResult.score ); - assessmentResult.setText( previouslyUsedKeywordsResult.text ); - - return assessmentResult; -}; - -export default PreviouslyUsedKeyword; +} From 6a7b865587880a85590909d927567e37f2beca6e Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 6 Sep 2024 10:03:07 +0300 Subject: [PATCH 061/313] remove isWooCommerceActive helper in favour if the redux selector --- packages/js/src/components/fills/MetaboxFill.js | 4 ++-- packages/js/src/helpers/isWooCommerceActive.js | 11 ----------- .../js/src/hooks/use-first-eligible-notification.js | 4 ++-- packages/js/src/redux/selectors/isWooSEO.js | 7 +++---- 4 files changed, 7 insertions(+), 19 deletions(-) delete mode 100644 packages/js/src/helpers/isWooCommerceActive.js diff --git a/packages/js/src/components/fills/MetaboxFill.js b/packages/js/src/components/fills/MetaboxFill.js index 6f8def95c95..95197220fae 100644 --- a/packages/js/src/components/fills/MetaboxFill.js +++ b/packages/js/src/components/fills/MetaboxFill.js @@ -23,7 +23,6 @@ import PremiumSEOAnalysisModal from "../modals/PremiumSEOAnalysisModal"; import KeywordUpsell from "../modals/KeywordUpsell"; import { BlackFridayProductEditorChecklistPromotion } from "../BlackFridayProductEditorChecklistPromotion"; import { BlackFridayPromotion } from "../BlackFridayPromotion"; -import { isWooCommerceActive } from "../../helpers/isWooCommerceActive"; import { withMetaboxWarningsCheck } from "../higherorder/withMetaboxWarningsCheck"; const BlackFridayProductEditorChecklistPromotionWithMetaboxWarningsCheck = withMetaboxWarningsCheck( BlackFridayProductEditorChecklistPromotion ); @@ -40,8 +39,9 @@ const BlackFridayPromotionWithMetaboxWarningsCheck = withMetaboxWarningsCheck( B export default function MetaboxFill( { settings } ) { const isTerm = useSelect( ( select ) => select( "yoast-seo/editor" ).getIsTerm(), [] ); const isProduct = useSelect( ( select ) => select( "yoast-seo/editor" ).getIsProduct(), [] ); + const isWooCommerceActive = useSelect( ( select ) => select( "yoast-seo/editor" ).getIsWooCommerceActive(), [] ); - const shouldShowWooCommerceChecklistPromo = isProduct && isWooCommerceActive(); + const shouldShowWooCommerceChecklistPromo = isProduct && isWooCommerceActive; return ( <> diff --git a/packages/js/src/helpers/isWooCommerceActive.js b/packages/js/src/helpers/isWooCommerceActive.js deleted file mode 100644 index f7a6afcccc1..00000000000 --- a/packages/js/src/helpers/isWooCommerceActive.js +++ /dev/null @@ -1,11 +0,0 @@ -import { get } from "lodash"; - -/** - * Checks if WooCommerce is active. - * - * @returns {boolean} True if WooCommerce is active. - */ -export const isWooCommerceActive = () => { - return get( window, "wpseoScriptData.metabox.isWooCommerceActive", false ); -}; - diff --git a/packages/js/src/hooks/use-first-eligible-notification.js b/packages/js/src/hooks/use-first-eligible-notification.js index 716a56f6048..044881c9291 100644 --- a/packages/js/src/hooks/use-first-eligible-notification.js +++ b/packages/js/src/hooks/use-first-eligible-notification.js @@ -2,7 +2,7 @@ import { BlackFridayPromotion } from "../components/BlackFridayPromotion"; import { BlackFridaySidebarChecklistPromotion } from "../components/BlackFridaySidebarChecklistPromotion"; import { TrustpilotReviewNotification, useTrustpilotReviewNotification } from "../components/trustpilot-review-notification"; import WebinarPromoNotification from "../components/WebinarPromoNotification"; -import { isWooCommerceActive } from "../helpers/isWooCommerceActive"; +import { getIsWooCommerceActive } from "../redux/selectors/isWooSEO"; import { shouldShowWebinarPromotionNotificationInSidebar } from "../helpers/shouldShowWebinarPromotionNotification"; /** @@ -44,7 +44,7 @@ export const useFirstEligibleNotification = ( { webinarIntroUrl } ) => { component: () => , }, { - getIsEligible: isWooCommerceActive, + getIsEligible: getIsWooCommerceActive, component: () => , }, { diff --git a/packages/js/src/redux/selectors/isWooSEO.js b/packages/js/src/redux/selectors/isWooSEO.js index 65c16f90b6a..ae686d605d1 100644 --- a/packages/js/src/redux/selectors/isWooSEO.js +++ b/packages/js/src/redux/selectors/isWooSEO.js @@ -1,6 +1,5 @@ import { get } from "lodash"; import { getIsProduct } from "./editorContext"; -import { isWooCommerceActive } from "../../helpers/isWooCommerceActive"; /** * Determines whether the WooCommerce SEO addon is active. @@ -14,7 +13,7 @@ export const getIsWooSeoActive = () => Boolean( get( window, "wpseoScriptData.is * * @returns {boolean} True if WooCommerce is active. */ -export const getIsWooCommerceActive = () => isWooCommerceActive(); +export const getIsWooCommerceActive = () => get( window, "wpseoScriptData.metabox.isWooCommerceActive", false ); /** * Determines whether the WooCommerce SEO addon is not active in a product page. @@ -24,8 +23,8 @@ export const getIsWooCommerceActive = () => isWooCommerceActive(); */ export const getIsWooSeoUpsell = ( state ) => { const isWooSeoActive = getIsWooSeoActive(); - const isWoocommerceActive = getIsWooCommerceActive(); + const isWooCommerceActive = getIsWooCommerceActive(); const isProductPage = getIsProduct( state ); - return ! isWooSeoActive && isWoocommerceActive && isProductPage; + return ! isWooSeoActive && isWooCommerceActive && isProductPage; }; From 83c4fcf22a27e3f647ab6f766f8b22151ef5bbe9 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 6 Sep 2024 10:04:30 +0300 Subject: [PATCH 062/313] restore comment --- packages/js/src/redux/selectors/isWooSEO.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/src/redux/selectors/isWooSEO.js b/packages/js/src/redux/selectors/isWooSEO.js index ae686d605d1..9b5113023bf 100644 --- a/packages/js/src/redux/selectors/isWooSEO.js +++ b/packages/js/src/redux/selectors/isWooSEO.js @@ -9,7 +9,7 @@ import { getIsProduct } from "./editorContext"; export const getIsWooSeoActive = () => Boolean( get( window, "wpseoScriptData.isWooCommerceSeoActive", false ) ); /** - * Checks if WooCommerce is active. Used also in premium. + * Checks if WooCommerce is active. * * @returns {boolean} True if WooCommerce is active. */ From e622c4d91589e2e499fc19328d2db04a9a018ecb Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 6 Sep 2024 10:51:36 +0300 Subject: [PATCH 063/313] increase test coverage --- .../Integrations/WooCommerce_Test.php | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Unit/Editors/Framework/Integrations/WooCommerce_Test.php b/tests/Unit/Editors/Framework/Integrations/WooCommerce_Test.php index c1e7647a105..6b1b66fa570 100644 --- a/tests/Unit/Editors/Framework/Integrations/WooCommerce_Test.php +++ b/tests/Unit/Editors/Framework/Integrations/WooCommerce_Test.php @@ -65,6 +65,30 @@ public function test_is_enabled( $this->assertSame( [ 'isWooCommerceActive' => $this->instance->is_enabled() ], $this->instance->to_legacy_array() ); } + /** + * Tests the to_array method. + * + * @dataProvider data_provider_is_enabled + * + * @param bool $woocommerce_enabled If the woocommerce plugin is enabled. + * @param bool $expected The expected outcome. + * + * @return void + */ + public function test_to_array( + bool $woocommerce_enabled, + bool $expected + ) { + + $this->woocommerce_conditional + ->expects( 'is_met' ) + ->times( 3 ) + ->andReturn( $woocommerce_enabled ); + + $this->assertSame( $expected, $this->instance->is_enabled() ); + $this->assertSame( [ 'isWooCommerceActive' => $this->instance->is_enabled() ], $this->instance->to_array() ); + } + /** * Data provider for test_is_enabled. * From 2acc2bfc1d6df7c2a5aa53a0f17d69e6c0c63dcb Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 6 Sep 2024 16:12:03 +0300 Subject: [PATCH 064/313] fixed webinar link in settings --- packages/js/src/initializers/settings-header.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js/src/initializers/settings-header.js b/packages/js/src/initializers/settings-header.js index cde18bc1bfc..a5ec2d01720 100644 --- a/packages/js/src/initializers/settings-header.js +++ b/packages/js/src/initializers/settings-header.js @@ -1,5 +1,5 @@ import { get } from "lodash"; -import { useSelect } from "@wordpress/data"; +import { select } from "@wordpress/data"; import { render } from "@wordpress/element"; import { ThemeProvider } from "styled-components"; import WebinarPromoNotification from "../components/WebinarPromoNotification"; @@ -14,7 +14,7 @@ import { shouldShowWebinarPromotionNotificationInDashboard } from "../helpers/sh const initSettingsHeader = () => { const reactRoot = document.getElementById( "yst-settings-header-root" ); const isRtl = Boolean( get( window, "wpseoScriptData.isRtl", false ) ); - const webinarIntroSettingsUrl = useSelect( select => select( "yoast-seo/editor" ).selectLink( "https://yoa.st/webinar-intro-settings" ), [] ); + const webinarIntroSettingsUrl = select( "yoast-seo/settings" ).selectLinkParams( "https://yoa.st/webinar-intro-settings" ); const isWooCommerce = get( window, "wpseoScriptData.isWooCommerceActive", "" ); if ( reactRoot ) { From 989fc72c93d3e2c3c0bd35174bcf1855a8371b4f Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 6 Sep 2024 16:35:59 +0300 Subject: [PATCH 065/313] fix args --- packages/js/src/initializers/settings-header.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/js/src/initializers/settings-header.js b/packages/js/src/initializers/settings-header.js index a5ec2d01720..629bf982b32 100644 --- a/packages/js/src/initializers/settings-header.js +++ b/packages/js/src/initializers/settings-header.js @@ -1,6 +1,7 @@ import { get } from "lodash"; import { select } from "@wordpress/data"; import { render } from "@wordpress/element"; +import { addQueryArgs } from "@wordpress/url"; import { ThemeProvider } from "styled-components"; import WebinarPromoNotification from "../components/WebinarPromoNotification"; import { BlackFridaySidebarChecklistPromotion } from "../components/BlackFridaySidebarChecklistPromotion"; @@ -14,7 +15,8 @@ import { shouldShowWebinarPromotionNotificationInDashboard } from "../helpers/sh const initSettingsHeader = () => { const reactRoot = document.getElementById( "yst-settings-header-root" ); const isRtl = Boolean( get( window, "wpseoScriptData.isRtl", false ) ); - const webinarIntroSettingsUrl = select( "yoast-seo/settings" ).selectLinkParams( "https://yoa.st/webinar-intro-settings" ); + const linkParams = select( "yoast-seo/settings" ).selectLinkParams(); + const webinarIntroSettingsUrl = addQueryArgs( "https://yoa.st/webinar-intro-settings", linkParams ); const isWooCommerce = get( window, "wpseoScriptData.isWooCommerceActive", "" ); if ( reactRoot ) { From d6539d594adae634842830fd4c9ba7b05710b5cc Mon Sep 17 00:00:00 2001 From: Enrico Battocchi Date: Sun, 8 Sep 2024 18:05:44 +0200 Subject: [PATCH 066/313] Add changelog heading + missing changelog line in 23.4 --- readme.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 682021a5d02..f3ccdf78a4e 100644 --- a/readme.txt +++ b/readme.txt @@ -276,7 +276,9 @@ Your question has most likely been answered on our help center: [yoast.com/help/ = 23.5 = -Release date: 2024-09-17 +Release date: 2024-09-24 + +Yoast SEO 23.5 brings more enhancements and bugfixes. [Find more information about our software releases and updates here](https://yoa.st/release-24-9-24). #### Enhancements @@ -299,6 +301,7 @@ Yoast SEO 23.4 brings more enhancements and bugfixes. [Find more information abo * Adds _так_ to the words recognized by the _transition words_ assessment in Russian. Props to @pavelmai83. * Improves the schema output by following the specification for the _SearchAction_ more strictly. * Re-enables the script concatenation that was disabled to prevent a bug with WordPress 5.5. +* Improves the look of the Yoast SEO metabox in the block editor by centering it and making it stand out more by using a background color. #### Bugfixes From 869ab69f8057c78fae2250e0aebb86cbd2999983 Mon Sep 17 00:00:00 2001 From: Enrico Battocchi Date: Sun, 8 Sep 2024 18:10:55 +0200 Subject: [PATCH 067/313] Add missing changelog line in 23.4 --- readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.txt b/readme.txt index 245feca32dd..5f18cedd5f5 100644 --- a/readme.txt +++ b/readme.txt @@ -286,6 +286,7 @@ Yoast SEO 23.4 brings more enhancements and bugfixes. [Find more information abo * Adds _так_ to the words recognized by the _transition words_ assessment in Russian. Props to @pavelmai83. * Improves the schema output by following the specification for the _SearchAction_ more strictly. * Re-enables the script concatenation that was disabled to prevent a bug with WordPress 5.5. +* Improves the look of the Yoast SEO metabox in the block editor by centering it and making it stand out more by using a background color. #### Bugfixes From 680c54981aaae11d8d461429ebf6d47cbccadb50 Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Mon, 9 Sep 2024 10:24:50 +0200 Subject: [PATCH 068/313] Converts the errors to use class syntax --- packages/yoastseo/src/errors/invalidType.js | 26 ++++++++--------- .../yoastseo/src/errors/missingArgument.js | 28 ++++++++----------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/packages/yoastseo/src/errors/invalidType.js b/packages/yoastseo/src/errors/invalidType.js index 425fafc0228..fd48e45e100 100644 --- a/packages/yoastseo/src/errors/invalidType.js +++ b/packages/yoastseo/src/errors/invalidType.js @@ -1,18 +1,14 @@ -import util from "util"; - /** - * Throws an invalid type error - * - * @param {string} message The message to show when the error is thrown - * - * @returns {void} + * The InvalidTypeError is thrown when an invalid type is passed as an argument. */ -function InvalidTypeError( message ) { - Error.captureStackTrace( this, this.constructor ); - this.name = this.constructor.name; - this.message = message; +export default class InvalidTypeError extends Error { + /** + * Constructs an InvalidTypeError. + * @param {string} message The message to show when the error is thrown. + * @constructor + */ + constructor( message ) { + super( message ); + this.name = "InvalidTypeError"; + } } - -util.inherits( InvalidTypeError, Error ); - -export default InvalidTypeError; diff --git a/packages/yoastseo/src/errors/missingArgument.js b/packages/yoastseo/src/errors/missingArgument.js index 4cecb3a4e79..7346740e07b 100644 --- a/packages/yoastseo/src/errors/missingArgument.js +++ b/packages/yoastseo/src/errors/missingArgument.js @@ -1,20 +1,14 @@ -import util from "util"; - /** - * Error that means that an argument should be passed that wasn't passed. - * - * @constructor - * - * @param {string} message The message for this error. - * - * @returns {void} + * The MissingArgumentError is thrown when a required argument is not passed. */ -function MissingArgumentError( message ) { - Error.captureStackTrace( this, this.constructor ); - this.name = this.constructor.name; - this.message = message; +export default class MissingArgumentError extends Error { + /** + * Constructs a MissingArgumentError. + * @param {string} message The message to show when the error is thrown. + * @constructor + */ + constructor( message ) { + super( message ); + this.name = "MissingArgumentError"; + } } - -util.inherits( MissingArgumentError, Error ); - -export default MissingArgumentError; From b31a6c7b8b6bcf00416b197f5f9a74089a68145a Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Mon, 9 Sep 2024 12:24:26 +0200 Subject: [PATCH 069/313] Add sidebar to new page --- admin/class-config.php | 4 +- css/src/new-dashboard.css | 86 +++++++++++++ packages/js/src/dashboard/app.js | 118 +++++++++++++++--- packages/js/src/dashboard/components/index.js | 2 + .../src/dashboard/components/route-layout.js | 48 +++++++ packages/js/src/dashboard/hooks/index.js | 2 + .../dashboard/hooks/use-select-dashboard.js | 12 ++ packages/js/src/dashboard/initialize.js | 5 +- packages/js/src/settings/app.js | 3 +- packages/js/src/settings/components/index.js | 2 - .../js/src/shared-admin/components/index.js | 2 + .../components/sidebar-navigation.js | 0 .../components/yoast-logo.svg | 0 .../first-time-configuration-integration.php | 2 +- 14 files changed, 263 insertions(+), 23 deletions(-) create mode 100644 css/src/new-dashboard.css create mode 100644 packages/js/src/dashboard/components/index.js create mode 100644 packages/js/src/dashboard/components/route-layout.js create mode 100644 packages/js/src/dashboard/hooks/index.js create mode 100644 packages/js/src/dashboard/hooks/use-select-dashboard.js rename packages/js/src/{settings => shared-admin}/components/sidebar-navigation.js (100%) rename packages/js/src/{settings => shared-admin}/components/yoast-logo.svg (100%) diff --git a/admin/class-config.php b/admin/class-config.php index 0f8ce558885..faa313db484 100644 --- a/admin/class-config.php +++ b/admin/class-config.php @@ -7,6 +7,7 @@ use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; +use Yoast\WP\SEO\Dashboard\User_Interface\General_Page_Integration; use Yoast\WP\SEO\Integrations\Academy_Integration; use Yoast\WP\SEO\Integrations\Settings_Integration; use Yoast\WP\SEO\Integrations\Support_Integration; @@ -54,7 +55,6 @@ public function init() { // Bail, this is managed in the applicable integration. return; } - add_action( 'admin_enqueue_scripts', [ $this, 'config_page_scripts' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'config_page_styles' ] ); } @@ -109,7 +109,7 @@ public function config_page_scripts() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; - if ( in_array( $page, [ WPSEO_Admin::PAGE_IDENTIFIER, 'wpseo_workouts' ], true ) ) { + if ( in_array( $page, [ WPSEO_Admin::PAGE_IDENTIFIER, General_Page_Integration::PAGE, 'wpseo_workouts' ], true ) ) { wp_enqueue_media(); $script_data['userEditUrl'] = add_query_arg( 'user_id', '{user_id}', admin_url( 'user-edit.php' ) ); diff --git a/css/src/new-dashboard.css b/css/src/new-dashboard.css new file mode 100644 index 00000000000..e56fb835b3d --- /dev/null +++ b/css/src/new-dashboard.css @@ -0,0 +1,86 @@ +body.seo_page_wpseo_page_dashboard_new { + @apply yst-bg-slate-100; + + /* Move WP footer behind our content. */ + z-index: -1; + + #wpcontent { + padding-left: 0 !important; + } + + #wpfooter { + @apply yst-pr-4; + + @media (min-width: 768px) { + @apply yst-pr-8; + + padding-left: 17rem; + } + } + + .wp-responsive-open #wpbody { + @media screen and (max-width: 782px) { + right: -190px; /* Override to not leave space between the mobile menu and the content. */ + } + } + + #modal-search .yst-modal__close { + margin-top: -0.25rem; + } + + &.sticky-menu { + .yst-root .yst-notifications--bottom-left { + @media (min-width: 783px) and (max-width: 962px) { + left: calc(160px + 2rem); /* Next to admin menu. 160px is the admin menu width. */ + } + } + + &.folded, &.auto-fold { + .yst-root .yst-notifications--bottom-left { + @media (min-width: 783px) and (max-width: 963px) { + left: calc(32px + 2rem); /* Next to admin menu. 32px is the collapsed admin menu width. */ + } + } + } + + &.folded { + .yst-root .yst-notifications--bottom-left { + @media (min-width: 962px) { + left: calc(32px + 2rem); /* Next to admin menu. 32px is the collapsed admin menu width. */ + } + } + } + } + + &:not(.sticky-menu) .wp-responsive-open .yst-root { + .yst-notifications--bottom-left { + @media (max-width: 783px) { + left: calc(190px + 2rem); /* Next to admin menu. 190px is the responsive admin menu width. */ + } + } + } + + .yst-root { + + .yst-mobile-navigation__top { + @media (min-width: 601px) and (max-width: 768px) { + top: 46px; + } + + @media (min-width: 783px) { + @apply yst-hidden; + } + } + + .yst-mobile-navigation__dialog { + z-index: 99999; + } + } + /* RTL */ + + &.rtl { + .yst-root .yst-replacevar .emoji-select-popover { + @apply yst-left-0 yst-right-auto; + } + } +} diff --git a/packages/js/src/dashboard/app.js b/packages/js/src/dashboard/app.js index 1544f134d07..e5852103f78 100644 --- a/packages/js/src/dashboard/app.js +++ b/packages/js/src/dashboard/app.js @@ -1,30 +1,116 @@ /* eslint-disable complexity */ +import { Transition } from "@headlessui/react"; +import { AdjustmentsIcon, BellIcon } from "@heroicons/react/outline"; import { __ } from "@wordpress/i18n"; -import { Paper, Title } from "@yoast/ui-library"; +import { Paper, useSvgAria, Title } from "@yoast/ui-library"; +import classNames from "classnames"; +import PropTypes from "prop-types"; +import { Link, Route, Routes, useLocation } from "react-router-dom"; +import FirstTimeConfigurationSteps from "../first-time-configuration/first-time-configuration-steps"; +import { useSelectDashboard } from "./hooks"; +import { SidebarNavigation, YoastLogo } from "../shared-admin/components"; +/** + * @param {string} [idSuffix] Extra id suffix. Can prevent double IDs on the page. + * @returns {JSX.Element} The menu element. + */ +const Menu = ( { idSuffix = "" } ) => { + const svgAriaProps = useSvgAria(); + const isPremium = useSelectDashboard( "selectPreference", [], "isPremium" ); + + return <> +
+ + + +
+
+
    + + +
+
+ ; +}; +Menu.propTypes = { + idSuffix: PropTypes.string, +}; +/** + * @returns {JSX.Element} The dashboard content placeholder. + */ +const Content = () => { + return <> +
+
+ { __( "Alert center", "wordpress-seo" ) } +

+ { __( "Monitor and manage potential SEO problems affecting your site and stay informed with important notifications and updates.", "wordpress-seo" ) } +

+
+
+
+
+ Content +
+
+ ; +}; /** * @returns {JSX.Element} The app component. */ const App = () => { + const { pathname } = useLocation(); + const isPremium = useSelectDashboard( "selectPreference", [], "isPremium" ); return ( -
- -
-
- { __( "Alert center", "wordpress-seo" ) } -

- { __( "Monitor and manage potential SEO problems affecting your site and stay informed with important notifications and updates.", "wordpress-seo" ) } -

-
-
-
-
- Content + +
+ ); }; diff --git a/packages/js/src/dashboard/components/index.js b/packages/js/src/dashboard/components/index.js new file mode 100644 index 00000000000..cd3070b84b2 --- /dev/null +++ b/packages/js/src/dashboard/components/index.js @@ -0,0 +1,2 @@ +export { default as RouteLayout } from "./route-layout"; +export { default as SidebarNavigation } from "./sidebar-navigation"; diff --git a/packages/js/src/dashboard/components/route-layout.js b/packages/js/src/dashboard/components/route-layout.js new file mode 100644 index 00000000000..a26be2330a6 --- /dev/null +++ b/packages/js/src/dashboard/components/route-layout.js @@ -0,0 +1,48 @@ +import { __, sprintf } from "@wordpress/i18n"; +import { Title } from "@yoast/ui-library"; +import PropTypes from "prop-types"; +import { Helmet } from "react-helmet"; +import { LiveAnnouncer, LiveMessage } from "react-aria-live"; + +/** + * @param {Object} props The properties. + * @param {JSX.node} children The children. + * @param {string} title The title. + * @param {JSX.node} [description] The description. + * @returns {JSX.Element} The route layout component. + */ +const RouteLayout = ( { + children, + title, + description, +} ) => { + const ariaLiveTitle = sprintf( + /* translators: 1: Settings' section title, 2: Yoast SEO */ + __( "%1$s Dashboard - %2$s", "wordpress-seo" ), + title, + "Yoast SEO" + ); + return ( + + + + Dashboard + +
+
+ { title } + { description &&

{ description }

} +
+
+ { children } +
+ ); +}; + +RouteLayout.propTypes = { + children: PropTypes.node.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.node, +}; + +export default RouteLayout; diff --git a/packages/js/src/dashboard/hooks/index.js b/packages/js/src/dashboard/hooks/index.js new file mode 100644 index 00000000000..0badbc32fe0 --- /dev/null +++ b/packages/js/src/dashboard/hooks/index.js @@ -0,0 +1,2 @@ + +export { default as useSelectDashboard } from "./use-select-dashboard"; diff --git a/packages/js/src/dashboard/hooks/use-select-dashboard.js b/packages/js/src/dashboard/hooks/use-select-dashboard.js new file mode 100644 index 00000000000..7cb4fcc6329 --- /dev/null +++ b/packages/js/src/dashboard/hooks/use-select-dashboard.js @@ -0,0 +1,12 @@ +import { useSelect } from "@wordpress/data"; +import { STORE_NAME } from "../constants"; + +/** + * @param {string} selector The name of the selector. + * @param {array} [deps] List of dependencies. + * @param {*} [args] Selector arguments. + * @returns {*} The result. + */ +const useSelectDashboard = ( selector, deps = [], ...args ) => useSelect( select => select( STORE_NAME )[ selector ]?.( ...args ), deps ); + +export default useSelectDashboard; diff --git a/packages/js/src/dashboard/initialize.js b/packages/js/src/dashboard/initialize.js index 7045b79d2bb..2952c7d50ba 100644 --- a/packages/js/src/dashboard/initialize.js +++ b/packages/js/src/dashboard/initialize.js @@ -5,6 +5,7 @@ import { render } from "@wordpress/element"; import { Root } from "@yoast/ui-library"; import { get } from "lodash"; import { LINK_PARAMS_NAME } from "../shared-admin/store"; +import { HashRouter } from "react-router-dom"; import App from "./app"; import { STORE_NAME } from "./constants"; import registerStore from "./store"; @@ -24,7 +25,9 @@ domReady( () => { render( - + + + , root diff --git a/packages/js/src/settings/app.js b/packages/js/src/settings/app.js index c2d0c63734c..6cb3144c307 100644 --- a/packages/js/src/settings/app.js +++ b/packages/js/src/settings/app.js @@ -11,7 +11,8 @@ import { map } from "lodash"; import PropTypes from "prop-types"; import { Link, Navigate, Route, Routes, useLocation } from "react-router-dom"; import { getPremiumBenefits } from "../helpers/get-premium-benefits"; -import { ErrorFallback, Notifications, Search, SidebarNavigation, SidebarRecommendations, YoastLogo } from "./components"; +import { SidebarNavigation, YoastLogo } from "../shared-admin/components"; +import { ErrorFallback, Notifications, Search, SidebarRecommendations } from "./components"; import { useRouterScrollRestore, useSelectSettings } from "./hooks"; import { AuthorArchives, diff --git a/packages/js/src/settings/components/index.js b/packages/js/src/settings/components/index.js index 9d880ba08ca..48464eadc79 100644 --- a/packages/js/src/settings/components/index.js +++ b/packages/js/src/settings/components/index.js @@ -14,7 +14,5 @@ export { default as NewsSeoAlert } from "./news-seo-alert"; export { default as OpenGraphDisabledAlert } from "./open-graph-disabled-alert"; export { default as RouteLayout } from "./route-layout"; export { default as Search } from "./search"; -export { default as SidebarNavigation } from "./sidebar-navigation"; export { default as SidebarRecommendations } from "./sidebar-recommendations"; export { default as ErrorFallback } from "./error-fallback"; -export { ReactComponent as YoastLogo } from "./yoast-logo.svg"; diff --git a/packages/js/src/shared-admin/components/index.js b/packages/js/src/shared-admin/components/index.js index efc31492597..e6dc3607d61 100644 --- a/packages/js/src/shared-admin/components/index.js +++ b/packages/js/src/shared-admin/components/index.js @@ -7,3 +7,5 @@ export { PremiumUpsellCard } from "./premium-upsell-card"; export { RecommendationsSidebar } from "./recommendations-sidebar"; export { VideoFlow } from "./video-flow"; export { AiFixAssessmentsUpsell } from "./ai-fix-assessments-upsell"; +export { default as SidebarNavigation } from "./sidebar-navigation"; +export { ReactComponent as YoastLogo } from "./yoast-logo.svg"; diff --git a/packages/js/src/settings/components/sidebar-navigation.js b/packages/js/src/shared-admin/components/sidebar-navigation.js similarity index 100% rename from packages/js/src/settings/components/sidebar-navigation.js rename to packages/js/src/shared-admin/components/sidebar-navigation.js diff --git a/packages/js/src/settings/components/yoast-logo.svg b/packages/js/src/shared-admin/components/yoast-logo.svg similarity index 100% rename from packages/js/src/settings/components/yoast-logo.svg rename to packages/js/src/shared-admin/components/yoast-logo.svg diff --git a/src/integrations/admin/first-time-configuration-integration.php b/src/integrations/admin/first-time-configuration-integration.php index 0bf5678cb95..43118a16a8b 100644 --- a/src/integrations/admin/first-time-configuration-integration.php +++ b/src/integrations/admin/first-time-configuration-integration.php @@ -137,7 +137,7 @@ public function add_first_time_configuration_tab( $dashboard_tabs ) { */ public function enqueue_assets() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved. - if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_dashboard' || \is_network_admin() ) { + if ( ! isset( $_GET['page'] ) || ( $_GET['page'] !== 'wpseo_dashboard' && $_GET['page'] !== 'wpseo_page_dashboard_new' ) || \is_network_admin() ) { return; } From 8311dd9dbb38224dc5d9c47693369df2185368f7 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Mon, 9 Sep 2024 12:24:37 +0200 Subject: [PATCH 070/313] Allow icon in submenu items. --- .../src/components/sidebar-navigation/submenu-item.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/ui-library/src/components/sidebar-navigation/submenu-item.js b/packages/ui-library/src/components/sidebar-navigation/submenu-item.js index 94ae0692b6c..35dbacb933d 100644 --- a/packages/ui-library/src/components/sidebar-navigation/submenu-item.js +++ b/packages/ui-library/src/components/sidebar-navigation/submenu-item.js @@ -10,7 +10,7 @@ import { useNavigationContext } from "./index"; * @param {Object} [props] Extra props. * @returns {JSX.Element} The submenu item element. */ -const SubmenuItem = ( { as: Component = "a", pathProp = "href", label, ...props } ) => { +const SubmenuItem = ( { as: Component = "a", pathProp = "href", label, icon: Icon = null, ...props } ) => { const { activePath, setMobileMenuOpen } = useNavigationContext(); const handleClick = useCallback( () => setMobileMenuOpen( false ), [ setMobileMenuOpen ] ); @@ -28,7 +28,12 @@ const SubmenuItem = ( { as: Component = "a", pathProp = "href", label, ...props onClick={ handleClick } { ...props } > - { label } + + { Icon && + + } + { label } + ); From b70dd8249c8a380becbd281791c34488f28da9ca Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Mon, 9 Sep 2024 13:19:23 +0200 Subject: [PATCH 071/313] Use conditional feature flag instead of filter feature flag. --- .../new-dashboard-ui-conditional.php | 18 ++++++++++++++++++ .../general-page-integration.php | 17 ++++++++--------- 2 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 src/conditionals/new-dashboard-ui-conditional.php diff --git a/src/conditionals/new-dashboard-ui-conditional.php b/src/conditionals/new-dashboard-ui-conditional.php new file mode 100644 index 00000000000..f8397bf3411 --- /dev/null +++ b/src/conditionals/new-dashboard-ui-conditional.php @@ -0,0 +1,18 @@ + */ public static function get_conditionals() { - return [ Admin_Conditional::class ]; + return [ Admin_Conditional::class, New_Dashboard_Ui_Conditional::class ]; } /** @@ -86,14 +87,12 @@ public static function get_conditionals() { * @return void */ public function register_hooks() { - if ( \apply_filters( 'wpseo_new_dashboard', false ) ) { - // Add page. - \add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ] ); - - // Are we on the dashboard page? - if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) { - \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); - } + // Add page. + \add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ] ); + + // Are we on the dashboard page? + if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) { + \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); } } From f69131345fbbe642ba339d5da84ae2a7f8fc4dce Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Mon, 9 Sep 2024 13:23:07 +0200 Subject: [PATCH 072/313] Update unit test to new situation --- .../General_Page_Integration_Test.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php b/tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php index b2e0789068a..ffbcbe66862 100644 --- a/tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php +++ b/tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php @@ -6,6 +6,7 @@ use Mockery; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; +use Yoast\WP\SEO\Conditionals\New_Dashboard_Ui_Conditional; use Yoast\WP\SEO\Dashboard\User_Interface\General_Page_Integration; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; @@ -108,6 +109,7 @@ public function test_get_conditionals() { $this->assertEquals( [ Admin_Conditional::class, + New_Dashboard_Ui_Conditional::class, ], General_Page_Integration::get_conditionals() ); @@ -120,7 +122,7 @@ public function test_get_conditionals() { */ public static function register_hooks_provider() { return [ - 'Not on dashboard' => [ + 'Not on dashboard' => [ 'current_page' => 'not dashboard', 'action_times' => 0, ], @@ -149,10 +151,6 @@ public function test_register_hooks_on_dashboard_page( $current_page, $action_ti ->with( 'wpseo_submenu_page', [ $this->instance, 'add_page' ] ) ->once(); - Monkey\Functions\expect( 'apply_filters' ) - ->with( 'wpseo_new_dashboard', false ) - ->once()->andReturnTrue(); - $this->current_page_helper ->expects( 'get_current_yoast_seo_page' ) ->once() @@ -253,13 +251,15 @@ public function expect_get_script_data() { ]; $this->product_helper - ->expects( 'is_premium' ) - ->once() - ->andReturn( false ); + ->expects( 'is_premium' ) + ->once() + ->andReturn( false ); Monkey\Functions\expect( 'is_rtl' )->once()->andReturn( false ); - Monkey\Functions\expect( 'plugins_url' )->once()->andReturn( 'http://basic.wordpress.test/wp-content/worspress-seo' ); + Monkey\Functions\expect( 'plugins_url' ) + ->once() + ->andReturn( 'http://basic.wordpress.test/wp-content/worspress-seo' ); $this->shortlink_helper ->expects( 'get_query_params' ) From a1248ba20bf04db6da3aa28920ec889bc6e2a7c7 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Tue, 10 Sep 2024 09:03:04 +0200 Subject: [PATCH 073/313] Make ftc css also work in new dashboard. --- css/src/first-time-configuration.css | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/css/src/first-time-configuration.css b/css/src/first-time-configuration.css index e1303b5889c..3e5eedf25f4 100644 --- a/css/src/first-time-configuration.css +++ b/css/src/first-time-configuration.css @@ -1,5 +1,4 @@ -#wpseo-first-time-configuration { - .yst-root { +#yoast-configuration { /* Override aggressive WordPress inputs styling */ .yst-input { @@ -60,5 +59,4 @@ .yst-checkbox__input { @apply before:yst-content-none !important; } - } } From 0685fe3ad43885b3607f31a307e377d9d0510721 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Tue, 10 Sep 2024 09:21:32 +0200 Subject: [PATCH 074/313] Rename to new_dashboard --- src/dashboard/user-interface/general-page-integration.php | 6 +++--- .../User_Interface/General_Page_Integration_Test.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/dashboard/user-interface/general-page-integration.php b/src/dashboard/user-interface/general-page-integration.php index 32d1a7956c7..e8f2ead3c25 100644 --- a/src/dashboard/user-interface/general-page-integration.php +++ b/src/dashboard/user-interface/general-page-integration.php @@ -20,7 +20,7 @@ class General_Page_Integration implements Integration_Interface { * * @TODO RENAME THIS TO SOMETHING SENSIBLE AFTER FEATURE FLAG PERIOD */ - public const PAGE = 'wpseo_page_dashboard_new'; + public const PAGE = 'wpseo_page_new_dashboard'; /** * Holds the WPSEO_Admin_Asset_Manager. @@ -112,7 +112,7 @@ public function add_page( $pages ) { [ 'wpseo_dashboard', '', - \__( 'Dashboard New', 'wordpress-seo' ), + \__( 'New dashboard', 'wordpress-seo' ), 'wpseo_manage_options', self::PAGE, [ $this, 'display_page' ], @@ -151,7 +151,7 @@ public function enqueue_assets() { * * @return array>> The script data. */ - public function get_script_data() { + private function get_script_data() { return [ 'preferences' => [ 'isPremium' => $this->product_helper->is_premium(), diff --git a/tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php b/tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php index ffbcbe66862..88e9281c338 100644 --- a/tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php +++ b/tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php @@ -21,7 +21,7 @@ */ final class General_Page_Integration_Test extends TestCase { - public const PAGE = 'wpseo_page_dashboard_new'; + public const PAGE = 'wpseo_page_new_dashboard'; /** * Holds the WPSEO_Admin_Asset_Manager. @@ -127,7 +127,7 @@ public static function register_hooks_provider() { 'action_times' => 0, ], 'On dashboard page' => [ - 'current_page' => 'wpseo_page_dashboard_new', + 'current_page' => 'wpseo_page_new_dashboard', 'action_times' => 1, ], ]; @@ -184,7 +184,7 @@ public function test_add_page() { $this->assertEquals( '', $pages[3][1] ); $this->assertEquals( 'Dashboard New', $pages[3][2] ); $this->assertEquals( 'wpseo_manage_options', $pages[3][3] ); - $this->assertEquals( 'wpseo_page_dashboard_new', $pages[3][4] ); + $this->assertEquals( 'wpseo_page_new_dashboard', $pages[3][4] ); $this->assertEquals( [ $this->instance, 'display_page' ], $pages[3][5] ); } From 1e5e28289a2c863f5e8038fcfc0c9131c09c35ee Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Tue, 10 Sep 2024 09:27:16 +0200 Subject: [PATCH 075/313] Rename class to new dashboard instead of general. --- ...php => new-dashboard-page-integration.php} | 4 +- ...> New_Dashboard_Page_Integration_Test.php} | 40 ++++--------------- 2 files changed, 10 insertions(+), 34 deletions(-) rename src/dashboard/user-interface/{general-page-integration.php => new-dashboard-page-integration.php} (97%) rename tests/Unit/Dashboard/User_Interface/{General_Page_Integration_Test.php => New_Dashboard_Page_Integration_Test.php} (84%) diff --git a/src/dashboard/user-interface/general-page-integration.php b/src/dashboard/user-interface/new-dashboard-page-integration.php similarity index 97% rename from src/dashboard/user-interface/general-page-integration.php rename to src/dashboard/user-interface/new-dashboard-page-integration.php index e8f2ead3c25..adc05a50a06 100644 --- a/src/dashboard/user-interface/general-page-integration.php +++ b/src/dashboard/user-interface/new-dashboard-page-integration.php @@ -11,9 +11,9 @@ use Yoast\WP\SEO\Integrations\Integration_Interface; /** - * Class General_Page_Integration. + * Class New_Dashboard_Page_Integration. */ -class General_Page_Integration implements Integration_Interface { +class New_Dashboard_Page_Integration implements Integration_Interface { /** * The page name. diff --git a/tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php similarity index 84% rename from tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php rename to tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php index 88e9281c338..ea5b61844c2 100644 --- a/tests/Unit/Dashboard/User_Interface/General_Page_Integration_Test.php +++ b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php @@ -7,7 +7,7 @@ use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\New_Dashboard_Ui_Conditional; -use Yoast\WP\SEO\Dashboard\User_Interface\General_Page_Integration; +use Yoast\WP\SEO\Dashboard\User_Interface\New_Dashboard_Page_Integration; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; @@ -15,11 +15,11 @@ use Yoast\WP\SEO\Tests\Unit\TestCase; /** - * Class General_Page_Integration_Test_Test. + * Class New_Dashboard_Page_Integration_Test_Test. * - * @coversDefaultClass \Yoast\WP\SEO\Dashboard\User_Interface\General_Page_Integration + * @coversDefaultClass \Yoast\WP\SEO\Dashboard\User_Interface\New_Dashboard_Page_Integration */ -final class General_Page_Integration_Test extends TestCase { +final class New_Dashboard_Page_Integration_Test extends TestCase { public const PAGE = 'wpseo_page_new_dashboard'; @@ -54,7 +54,7 @@ final class General_Page_Integration_Test extends TestCase { /** * The class under test. * - * @var General_Page_Integration + * @var New_Dashboard_Page_Integration */ protected $instance; @@ -71,7 +71,7 @@ public function set_up() { $this->product_helper = Mockery::mock( Product_Helper::class ); $this->shortlink_helper = Mockery::mock( Short_Link_Helper::class ); - $this->instance = new General_Page_Integration( + $this->instance = new New_Dashboard_Page_Integration( $this->asset_manager, $this->current_page_helper, $this->product_helper, @@ -111,7 +111,7 @@ public function test_get_conditionals() { Admin_Conditional::class, New_Dashboard_Ui_Conditional::class, ], - General_Page_Integration::get_conditionals() + New_Dashboard_Page_Integration::get_conditionals() ); } @@ -182,7 +182,7 @@ public function test_add_page() { // Assert that the new page was added at index 3. $this->assertEquals( 'wpseo_dashboard', $pages[3][0] ); $this->assertEquals( '', $pages[3][1] ); - $this->assertEquals( 'Dashboard New', $pages[3][2] ); + $this->assertEquals( 'New dashboard', $pages[3][2] ); $this->assertEquals( 'wpseo_manage_options', $pages[3][3] ); $this->assertEquals( 'wpseo_page_new_dashboard', $pages[3][4] ); $this->assertEquals( [ $this->instance, 'display_page' ], $pages[3][5] ); @@ -269,28 +269,4 @@ public function expect_get_script_data() { return $link_params; } - /** - * Test for get_script_data that is used in enqueue_assets. - * - * @covers ::get_script_data - * - * @return void - */ - public function test_get_script_data() { - $link_params = $this->expect_get_script_data(); - $expected = [ - 'preferences' => [ - 'isPremium' => false, - 'isRtl' => false, - 'pluginUrl' => 'http://basic.wordpress.test/wp-content/worspress-seo', - 'upsellSettings' => [ - 'actionId' => 'load-nfd-ctb', - 'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2', - ], - ], - 'linkParams' => $link_params, - ]; - - $this->assertSame( $expected, $this->instance->get_script_data() ); - } } From 80eeb9c9978fca3c01ef705eff2f5883c2c1637a Mon Sep 17 00:00:00 2001 From: Igor <35524806+igorschoester@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:20:38 +0200 Subject: [PATCH 076/313] Refactor menu item Don't override the SidebarNavigation from the UI library (weird bugs came to life, maybe import order - not sure) But there is not need to override it, just using a separate component that wraps the original is fine --- packages/js/src/dashboard/app.js | 14 +++--- packages/js/src/settings/app.js | 44 ++++++++++--------- .../js/src/shared-admin/components/index.js | 2 +- ...idebar-navigation.js => menu-item-link.js} | 12 ++--- 4 files changed, 35 insertions(+), 37 deletions(-) rename packages/js/src/shared-admin/components/{sidebar-navigation.js => menu-item-link.js} (64%) diff --git a/packages/js/src/dashboard/app.js b/packages/js/src/dashboard/app.js index e5852103f78..efaf66a6a54 100644 --- a/packages/js/src/dashboard/app.js +++ b/packages/js/src/dashboard/app.js @@ -3,13 +3,13 @@ import { Transition } from "@headlessui/react"; import { AdjustmentsIcon, BellIcon } from "@heroicons/react/outline"; import { __ } from "@wordpress/i18n"; -import { Paper, useSvgAria, Title } from "@yoast/ui-library"; +import { Paper, SidebarNavigation, Title, useSvgAria } from "@yoast/ui-library"; import classNames from "classnames"; import PropTypes from "prop-types"; import { Link, Route, Routes, useLocation } from "react-router-dom"; import FirstTimeConfigurationSteps from "../first-time-configuration/first-time-configuration-steps"; +import { MenuItemLink, YoastLogo } from "../shared-admin/components"; import { useSelectDashboard } from "./hooks"; -import { SidebarNavigation, YoastLogo } from "../shared-admin/components"; /** * @param {string} [idSuffix] Extra id suffix. Can prevent double IDs on the page. @@ -32,10 +32,12 @@ const Menu = ( { idSuffix = "" } ) => {
    - - + +
; diff --git a/packages/js/src/settings/app.js b/packages/js/src/settings/app.js index 6cb3144c307..b3430909e94 100644 --- a/packages/js/src/settings/app.js +++ b/packages/js/src/settings/app.js @@ -4,15 +4,15 @@ import { AdjustmentsIcon, ArrowNarrowRightIcon, ColorSwatchIcon, DesktopComputer import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/solid"; import { createInterpolateElement, useCallback, useMemo } from "@wordpress/element"; import { __, sprintf } from "@wordpress/i18n"; -import { Badge, Button, ChildrenLimiter, ErrorBoundary, Paper, Title, useBeforeUnload, useSvgAria } from "@yoast/ui-library"; +import { Badge, Button, ChildrenLimiter, ErrorBoundary, Paper, SidebarNavigation, Title, useBeforeUnload, useSvgAria } from "@yoast/ui-library"; import classNames from "classnames"; import { useFormikContext } from "formik"; import { map } from "lodash"; import PropTypes from "prop-types"; import { Link, Navigate, Route, Routes, useLocation } from "react-router-dom"; import { getPremiumBenefits } from "../helpers/get-premium-benefits"; -import { SidebarNavigation, YoastLogo } from "../shared-admin/components"; -import { ErrorFallback, Notifications, Search, SidebarRecommendations } from "./components"; +import { MenuItemLink, YoastLogo } from "../shared-admin/components"; +import { ErrorFallback, Notifications, Search, SidebarRecommendations } from "./components"; import { useRouterScrollRestore, useSelectSettings } from "./hooks"; import { AuthorArchives, @@ -82,14 +82,14 @@ const Menu = ( { postTypes, taxonomies, idSuffix = "" } ) => { icon={ DesktopComputerIcon } label={ __( "General", "wordpress-seo" ) } > - - - + + - + { label={ __( "Content types", "wordpress-seo" ) } > - + { map( postTypes, ( { name, route, label, isNew } ) => ( - @@ -118,7 +118,7 @@ const Menu = ( { postTypes, taxonomies, idSuffix = "" } ) => { > { map( taxonomies, taxonomy => ( - @@ -136,18 +136,18 @@ const Menu = ( { postTypes, taxonomies, idSuffix = "" } ) => { label={ __( "Advanced", "wordpress-seo" ) } defaultOpen={ false } > - - - - - - - - + + + + + + +
; @@ -170,14 +170,16 @@ const PremiumUpsellList = () => { return ( - { isBlackFriday &&
+ { isBlackFriday &&
{ __( "BLACK FRIDAY", "wordpress-seo" ) }
{ __( "30% OFF", "wordpress-seo" ) }
}
{ sprintf( - /* translators: %s expands to "Yoast SEO" Premium */ + /* translators: %s expands to "Yoast SEO" Premium */ __( "Upgrade to %s", "wordpress-seo" ), "Yoast SEO Premium" ) } @@ -200,7 +202,7 @@ const PremiumUpsellList = () => { { ...premiumUpsellConfig } > { isBlackFriday ? __( "Claim your 30% off now!", "wordpress-seo" ) : sprintf( - /* translators: %s expands to "Yoast SEO" Premium */ + /* translators: %s expands to "Yoast SEO" Premium */ __( "Explore %s now!", "wordpress-seo" ), "Yoast SEO Premium" ) } diff --git a/packages/js/src/shared-admin/components/index.js b/packages/js/src/shared-admin/components/index.js index e6dc3607d61..4cbaeb72147 100644 --- a/packages/js/src/shared-admin/components/index.js +++ b/packages/js/src/shared-admin/components/index.js @@ -2,10 +2,10 @@ export { AcademyUpsellCard } from "./academy-upsell-card"; export { AiGenerateTitlesAndDescriptionsUpsell } from "./ai-generate-titles-and-descriptions-upsell"; export { FieldsetLayout } from "./fieldset-layout"; export { ReactComponent as G2LogoWhite } from "./g2-logo-white.svg"; +export { MenuItemLink } from "./menu-item-link"; export { OutboundLink } from "./outbound-link"; export { PremiumUpsellCard } from "./premium-upsell-card"; export { RecommendationsSidebar } from "./recommendations-sidebar"; export { VideoFlow } from "./video-flow"; export { AiFixAssessmentsUpsell } from "./ai-fix-assessments-upsell"; -export { default as SidebarNavigation } from "./sidebar-navigation"; export { ReactComponent as YoastLogo } from "./yoast-logo.svg"; diff --git a/packages/js/src/shared-admin/components/sidebar-navigation.js b/packages/js/src/shared-admin/components/menu-item-link.js similarity index 64% rename from packages/js/src/shared-admin/components/sidebar-navigation.js rename to packages/js/src/shared-admin/components/menu-item-link.js index 6c98ccedf61..6ebe3b87c21 100644 --- a/packages/js/src/shared-admin/components/sidebar-navigation.js +++ b/packages/js/src/shared-admin/components/menu-item-link.js @@ -4,24 +4,18 @@ import { replace } from "lodash"; import PropTypes from "prop-types"; import { Link } from "react-router-dom"; -const PureSubmenuItem = SidebarNavigation.SubmenuItem; - /** * @param {Object} props The props. * @param {string} to The path to link to. * @param {string} [idSuffix] Extra id suffix. Can prevent double IDs on the page. * @returns {JSX.Element} The submenu item element. */ -const SubmenuItem = ( { to, idSuffix = "", ...props } ) => { +export const MenuItemLink = ( { to, idSuffix = "", ...props } ) => { const id = useMemo( () => replace( replace( `link-${ to }`, "/", "-" ), "--", "-" ), [ to ] ); - return <PureSubmenuItem as={ Link } pathProp="to" id={ `${ id }${ idSuffix }` } to={ to } { ...props } />; + return <SidebarNavigation.SubmenuItem as={ Link } pathProp="to" id={ `${ id }${ idSuffix }` } to={ to } { ...props } />; }; -SubmenuItem.propTypes = { +MenuItemLink.propTypes = { to: PropTypes.string.isRequired, idSuffix: PropTypes.string, }; - -SidebarNavigation.SubmenuItem = SubmenuItem; - -export default SidebarNavigation; From 8e6f239cd6ff414b24745a5a95e1d0bf503fde8e Mon Sep 17 00:00:00 2001 From: Vraja Das <vraja.pro@gmail.com> Date: Tue, 10 Sep 2024 14:28:43 +0300 Subject: [PATCH 077/313] implement useAnchor --- packages/js/package.json | 2 +- packages/js/src/inline-links/edit-link.js | 2 ++ packages/js/src/inline-links/inline.js | 38 +++++++---------------- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/packages/js/package.json b/packages/js/package.json index 1ccb5955d1c..ee5a543432c 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "cd ../.. && wp-scripts build --config config/webpack/webpack.config.js", "test": "jest", - "lint": "eslint src tests --max-warnings=69" + "lint": "eslint src tests --max-warnings=59" }, "dependencies": { "@draft-js-plugins/mention": "^5.0.0", diff --git a/packages/js/src/inline-links/edit-link.js b/packages/js/src/inline-links/edit-link.js index d9d069eaeaf..7e24382b96e 100644 --- a/packages/js/src/inline-links/edit-link.js +++ b/packages/js/src/inline-links/edit-link.js @@ -1,3 +1,4 @@ +/* eslint-disable react/prop-types */ /** * WordPress dependencies */ @@ -169,6 +170,7 @@ export const link = { activeAttributes={ activeAttributes } value={ value } onChange={ onChange } + contentRef={ this.props.contentRef } /> ) } </Fragment> diff --git a/packages/js/src/inline-links/inline.js b/packages/js/src/inline-links/inline.js index 9683af4dfbe..017e1a27e80 100644 --- a/packages/js/src/inline-links/inline.js +++ b/packages/js/src/inline-links/inline.js @@ -11,7 +11,7 @@ import { useMemo, useState, useCallback } from "@wordpress/element"; import { __, sprintf } from "@wordpress/i18n"; import { withSpokenMessages, Popover } from "@wordpress/components"; import { prependHTTP } from "@wordpress/url"; -import { create, insert, isCollapsed, applyFormat } from "@wordpress/rich-text"; +import { applyFormat, create, insert, isCollapsed, useAnchor } from "@wordpress/rich-text"; /** * Internal dependencies @@ -19,6 +19,7 @@ import { create, insert, isCollapsed, applyFormat } from "@wordpress/rich-text"; import { createLinkFormat, isValidHref } from "./utils"; import HelpLink from "../components/HelpLink"; import createInterpolateElement from "../helpers/createInterpolateElement"; +import { link as linkSettings } from "./edit-link"; /** * Component to render the inline link UI. @@ -44,6 +45,7 @@ function InlineLinkUI( { onChange, speak, stopAddingLink, + contentRef, } ) { /** * A unique key is generated when switching between editing and not editing @@ -71,30 +73,13 @@ function InlineLinkUI( { */ const [ nextLinkValue, setNextLinkValue ] = useState(); - const anchorRef = useMemo( () => { - const selection = window.getSelection(); - - if ( ! selection.rangeCount ) { - return; - } - - const range = selection.getRangeAt( 0 ); - - if ( addingLink && ! isActive ) { - return range; - } - - let element = range.startContainer; - - // If the caret is right before the element, select the next element. - element = element.nextElementSibling || element; - - while ( element.nodeType !== window.Node.ELEMENT_NODE ) { - element = element.parentNode; - } - - return element.closest( "a" ); - }, [ addingLink, value.start, value.end ] ); + const anchor = useAnchor( { + editableContentElement: contentRef.current, + settings: { + ...linkSettings, + isActive, + }, + } ); const linkValue = { url: activeAttributes.url, @@ -338,7 +323,7 @@ function InlineLinkUI( { return ( <Popover key={ mountingKey } - anchor={ anchorRef } + anchor={ anchor } focusOnMount={ addingLink ? "firstElement" : false } onClose={ stopAddingLink } position="bottom center" @@ -363,6 +348,7 @@ InlineLinkUI.propTypes = { onChange: PropTypes.func, speak: PropTypes.func.isRequired, stopAddingLink: PropTypes.func.isRequired, + contentRef: PropTypes.object, }; export default withSpokenMessages( InlineLinkUI ); From 1c2cc3527bd271da42d307c01e99b3581344d7ff Mon Sep 17 00:00:00 2001 From: marinakoleva <marina@yoast.com> Date: Tue, 10 Sep 2024 13:43:43 +0200 Subject: [PATCH 078/313] adding additional entries to the transition words list for Turkish and English --- .../languages/en/config/transitionWords.js | 4 ++-- .../languages/tr/config/transitionWords.js | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/yoastseo/src/languageProcessing/languages/en/config/transitionWords.js b/packages/yoastseo/src/languageProcessing/languages/en/config/transitionWords.js index afbd9bb76a7..e5ad4f3b4c2 100644 --- a/packages/yoastseo/src/languageProcessing/languages/en/config/transitionWords.js +++ b/packages/yoastseo/src/languageProcessing/languages/en/config/transitionWords.js @@ -11,7 +11,7 @@ export const singleWords = [ "accordingly", "additionally", "afterward", "afterw "shortly", "significantly", "similarly", "simultaneously", "since", "so", "soon", "specifically", "still", "straightaway", "subsequently", "surely", "surprisingly", "than", "then", "thereafter", "therefore", "thereupon", "thirdly", "though", "thus", "till", "undeniably", "undoubtedly", "unless", "unlike", "unquestionably", "until", "when", "whenever", - "whereas", "while" ]; + "whereas", "while", "whether", "if", "actually" ]; export const multipleWords = [ "above all", "after all", "after that", "all in all", "all of a sudden", "all things considered", "analogous to", "although this may be true", "analogous to", "another key point", "as a matter of fact", "as a result", "as an illustration", "as can be seen", "as has been noted", "as I have noted", "as I have said", "as I have shown", @@ -40,7 +40,7 @@ export const multipleWords = [ "above all", "after all", "after that", "all in a "to summarize", "to that end", "to the end that", "to this end", "together with", "under those circumstances", "until now", "up against", "up to the present time", "vis a vis", "what's more", "while it may be true", "while this may be true", "with attention to", "with the result that", "with this in mind", "with this intention", "with this purpose in mind", - "without a doubt", "without delay", "without doubt", "without reservation" ]; + "without a doubt", "without delay", "without doubt", "without reservation", "according to", "whether or" ]; export const allWords = singleWords.concat( multipleWords ); diff --git a/packages/yoastseo/src/languageProcessing/languages/tr/config/transitionWords.js b/packages/yoastseo/src/languageProcessing/languages/tr/config/transitionWords.js index a8cc460d5bd..5228b00dc17 100644 --- a/packages/yoastseo/src/languageProcessing/languages/tr/config/transitionWords.js +++ b/packages/yoastseo/src/languageProcessing/languages/tr/config/transitionWords.js @@ -8,7 +8,8 @@ export const singleWords = [ "fakat", "halbuki", "hatta", "üstelik", "ancak", " "genelde", "dolayısıyla", "gelgelelim", "aslında", "doğrusu", "mamafih", "binaenaleyh", "evvelce", "önceden", "şöylelikle", "örneğin", "mesela", "nitekim", "mademki", "şimdi", "halihazırda", "i̇laveten", "aynen", "nazaran", "nedeniyle", "yüzünden", "umumiyetle", "ekseriye", "amacıyla", "gayesiyle", "velhasıl", "ezcümle", "özetlersek", "etraflıca", "tafsilatlı", "genişçe", "bilfiil", "filhakika", "evvela", "i̇lkin", "en önce", - "birincisi", "i̇kincisi", "üçüncüsü", "sonuncusu", "tıpkı", "topyekun", "hem", "ne", "kah", "ister", "ya", "gerek", "ha" ]; + "birincisi", "i̇kincisi", "üçüncüsü", "sonuncusu", "tıpkı", "topyekun", "hem", "ne", "kah", "ister", "ya", "gerekse", "ha", "sayesinde", "sebebiyle", + "üzere", "göre", "uyarınca", "halen", "gerçekten", "madem", "yoksa" ]; export const multipleWords = [ "o halde", "bundan böyle", "demek ki", "ne yazık ki", "görüldüğü gibi", "i̇lk olarak", "son olarak", "ne var ki", "buna rağmen", "yine de", "başka bir deyişle", "açıklamak gerekirse", "özetlemek gerekirse", "kısaca söylemek gerekirse", "görüldüğü gibi", @@ -34,7 +35,8 @@ export const multipleWords = [ "o halde", "bundan böyle", "demek ki", "ne yazı "sözün özü", "en nihayetinde", "uzun uzadıya", "her iki durumda da", "özü itibariyle", "amacı ile", "olması için", "başka bir ifadeyle", "diğer bir deyişle", "i̇lk önce", "bir yandan", "bir taraftan", "hatırlatmak gerekirse", "bu bağlamda", "gel gelelim", "her şey hesaba katılırsa", "bütüne bakıldığında", "belirtildiği gibi", "bir başka ifadeyle", "lafı toparlamak gerekirse", "bu düşünceyle", "bu maksatla", "bu doğrultuda", - "bu niyetle", "hem de", "ne de", "ya da", "gerekse de" ]; + "bu niyetle", "hem de", "ne de", "ya da", "gerekse de", "aksi durumda", "bu durum", "olsun olmasın", "olup olmadığı", "diğer yandan", + "öte yandan", "ne ne", "gerek gerek" ]; export const allWords = singleWords.concat( multipleWords ); From 770d9a24f14f39a0255a2648f3630649a5226ae8 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden <thijsvanderheijden2@gmail.com> Date: Tue, 10 Sep 2024 13:46:45 +0200 Subject: [PATCH 079/313] CS... --- .../User_Interface/New_Dashboard_Page_Integration_Test.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php index ea5b61844c2..a6f98f9a376 100644 --- a/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php +++ b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php @@ -268,5 +268,4 @@ public function expect_get_script_data() { return $link_params; } - } From 8e60625a5ed73e2e644dd499007f8d838c2b5acc Mon Sep 17 00:00:00 2001 From: Vraja Das <vraja.pro@gmail.com> Date: Tue, 10 Sep 2024 14:52:45 +0300 Subject: [PATCH 080/313] update @wordpress/rich-text dependency --- packages/js/package.json | 2 +- yarn.lock | 219 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 217 insertions(+), 4 deletions(-) diff --git a/packages/js/package.json b/packages/js/package.json index ee5a543432c..788c14007aa 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -27,7 +27,7 @@ "@wordpress/i18n": "^4.14.0", "@wordpress/is-shallow-equal": "^3.1.1", "@wordpress/plugins": "^3.1.2", - "@wordpress/rich-text": "^3.25.3", + "@wordpress/rich-text": "^7.0.2", "@wordpress/server-side-render": "^1.21.1", "@wordpress/url": "^3.17.0", "@yoast/analysis-report": "^1.21.0", diff --git a/yarn.lock b/yarn.lock index 1a23c2f22ed..8ed1bd5f3b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5849,6 +5849,13 @@ dependencies: "@types/react" "*" +"@types/react-dom@^18.2.25": + version "18.3.0" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0" + integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^16", "@types/react@^16.9.0": version "16.14.5" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.5.tgz#2c39b5cadefaf4829818f9219e5e093325979f4d" @@ -5885,6 +5892,14 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^18.2.79": + version "18.3.5" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.5.tgz#5f524c2ad2089c0ff372bbdabc77ca2c4dbadf8f" + integrity sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@types/resolve@^1.20.2": version "1.20.6" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.6.tgz#e6e60dad29c2c8c206c026e6dd8d6d1bdda850b8" @@ -6419,6 +6434,15 @@ "@wordpress/dom-ready" "^3.48.0" "@wordpress/i18n" "^4.48.0" +"@wordpress/a11y@^4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/a11y/-/a11y-4.7.0.tgz#003dca403cdeaa274df97809f9c80660528f4cfe" + integrity sha512-qeh8TcJNNr9M0XL3OUDawBRrZypNLsnLjcXEBd6jp8Y4kOWxowmDDT6re1uToPdYTLLW2PZmZeBLYR9OS7pgpw== + dependencies: + "@babel/runtime" "^7.16.0" + "@wordpress/dom-ready" "^4.7.0" + "@wordpress/i18n" "^5.7.0" + "@wordpress/api-fetch@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@wordpress/api-fetch/-/api-fetch-4.0.0.tgz#86fa1ab40f320dc972d32635ed60059e90678a0d" @@ -6829,6 +6853,25 @@ mousetrap "^1.6.5" use-memo-one "^1.1.1" +"@wordpress/compose@^7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/compose/-/compose-7.7.0.tgz#3eec3a0027f72dbb4d03a2a64e956347169f79a3" + integrity sha512-TjhGcw9n/XbiMT63POESs1TF9O6eQRVhAPrMan5t2yusQbog5KLk4TetOasIWxD80pu5sg9P5NuupuU/oSEBYQ== + dependencies: + "@babel/runtime" "^7.16.0" + "@types/mousetrap" "^1.6.8" + "@wordpress/deprecated" "^4.7.0" + "@wordpress/dom" "^4.7.0" + "@wordpress/element" "^6.7.0" + "@wordpress/is-shallow-equal" "^5.7.0" + "@wordpress/keycodes" "^4.7.0" + "@wordpress/priority-queue" "^3.7.0" + "@wordpress/undo-manager" "^1.7.0" + change-case "^4.1.2" + clipboard "^2.0.11" + mousetrap "^1.6.5" + use-memo-one "^1.1.1" + "@wordpress/data-controls@^1.21.3": version "1.21.3" resolved "https://registry.yarnpkg.com/@wordpress/data-controls/-/data-controls-1.21.3.tgz#a82e2dedf7a8d190b3cb086dd13fe49a88e74149" @@ -6839,6 +6882,27 @@ "@wordpress/data" "^4.27.3" "@wordpress/deprecated" "^2.12.3" +"@wordpress/data@^10.7.0": + version "10.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/data/-/data-10.7.0.tgz#1b6c6c5bb1d0861db88070ed4ad96321a20c4f55" + integrity sha512-0NqDYIMOHdilSYoH6LRaq1CHcWlJiGP6xxkjI6pu2ZEf5mo9S/UblLCzVwaZMnhae/ZxEsgQQIypIQJJqor9uw== + dependencies: + "@babel/runtime" "^7.16.0" + "@wordpress/compose" "^7.7.0" + "@wordpress/deprecated" "^4.7.0" + "@wordpress/element" "^6.7.0" + "@wordpress/is-shallow-equal" "^5.7.0" + "@wordpress/priority-queue" "^3.7.0" + "@wordpress/private-apis" "^1.7.0" + "@wordpress/redux-routine" "^5.7.0" + deepmerge "^4.3.0" + equivalent-key-map "^0.2.2" + is-plain-object "^5.0.0" + is-promise "^4.0.0" + redux "^4.1.2" + rememo "^4.0.2" + use-memo-one "^1.1.1" + "@wordpress/data@^4.10.0": version "4.10.0" resolved "https://registry.yarnpkg.com/@wordpress/data/-/data-4.10.0.tgz#2161af0a3c1fb0fb2b63f95beb36a64b8acf65d6" @@ -6991,6 +7055,14 @@ "@babel/runtime" "^7.16.0" "@wordpress/hooks" "^3.3.1" +"@wordpress/deprecated@^4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/deprecated/-/deprecated-4.7.0.tgz#ae4f1f4c785ccc8d321c1a082e52f16aaa7fed52" + integrity sha512-FMtYPk+yxvEAs1LDBHk1yHz48vlp/TWrTQeMph5ov7dpw4Xgfd9darXorsl4zudJVrB+Nn6dYrPmrS08rAXtQQ== + dependencies: + "@babel/runtime" "^7.16.0" + "@wordpress/hooks" "^4.7.0" + "@wordpress/dom-ready@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@wordpress/dom-ready/-/dom-ready-1.2.0.tgz#d95877eb6f929532ec971983b55e731babb78d9c" @@ -7019,6 +7091,13 @@ dependencies: "@babel/runtime" "^7.16.0" +"@wordpress/dom-ready@^4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/dom-ready/-/dom-ready-4.7.0.tgz#8654aae7d5d8ea421fbb77547abebab081f84a17" + integrity sha512-moMbRCPNfgCc0dT0waEr0renEsptnDMV89fGpMijA66IyvYoYsxDT57w2JqHiaKbTvbIBmgdNgDjcVgZGv5JoA== + dependencies: + "@babel/runtime" "^7.16.0" + "@wordpress/dom@^2.18.0": version "2.18.0" resolved "https://registry.yarnpkg.com/@wordpress/dom/-/dom-2.18.0.tgz#8394f42e86dcca3f3bcddb805d05fdd65e9cfd07" @@ -7051,6 +7130,14 @@ "@babel/runtime" "^7.16.0" lodash "^4.17.21" +"@wordpress/dom@^4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/dom/-/dom-4.7.0.tgz#a1aa50bdd2cc19dcddc2ff6a64fb70285186cc4c" + integrity sha512-lGPEJHSHOT5Y9gWsX8V2tcsd5shDCTJqDxzL+pwDTfEsi/Os52nZCvzmavzGwRDzlm2Wmd3wNW+k/UCS/PhmXQ== + dependencies: + "@babel/runtime" "^7.16.0" + "@wordpress/deprecated" "^4.7.0" + "@wordpress/e2e-test-utils-playwright@^0.13.0": version "0.13.0" resolved "https://registry.yarnpkg.com/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.13.0.tgz#c06d6d6288ac5a78c9148c7084a0db1bd10211c0" @@ -7167,6 +7254,20 @@ react "^18.2.0" react-dom "^18.2.0" +"@wordpress/element@^6.7.0": + version "6.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/element/-/element-6.7.0.tgz#8d8ac6568909135b8b86131ee689a2724f36df05" + integrity sha512-d0kiN8DCNDNoh5P5xLb496amoadvjsSnkyJHmQsw17qP4dHZaSLONiMi9yh3NQlwIu0pcbbn3WI/9ENA79HlFQ== + dependencies: + "@babel/runtime" "^7.16.0" + "@types/react" "^18.2.79" + "@types/react-dom" "^18.2.25" + "@wordpress/escape-html" "^3.7.0" + change-case "^4.1.2" + is-plain-object "^5.0.0" + react "^18.3.0" + react-dom "^18.3.0" + "@wordpress/escape-html@^1.12.2": version "1.12.2" resolved "https://registry.yarnpkg.com/@wordpress/escape-html/-/escape-html-1.12.2.tgz#dcc92178bacc69952cde9bb8fb1cbbea9deb2cc3" @@ -7209,6 +7310,13 @@ dependencies: "@babel/runtime" "^7.16.0" +"@wordpress/escape-html@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/escape-html/-/escape-html-3.7.0.tgz#cbd5a4ae4d68acd37ba4895b0bfb6eaa5b8b72be" + integrity sha512-VqLQGNMs1BF6LnS+5eNjpM/sCUQhjn4QOfhDlWdVDi0ZxpZgssPzKhJ1ils/7FC0qF3vrMg8EH5xXxw2xz8A/w== + dependencies: + "@babel/runtime" "^7.16.0" + "@wordpress/eslint-plugin@^17.2.0": version "17.2.0" resolved "https://registry.yarnpkg.com/@wordpress/eslint-plugin/-/eslint-plugin-17.2.0.tgz#8fb56d2f530e5d995f7239b2571e078a907b2bb3" @@ -7295,6 +7403,13 @@ dependencies: "@babel/runtime" "^7.16.0" +"@wordpress/hooks@^4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/hooks/-/hooks-4.7.0.tgz#172d101ef3298bbf84b81723c071c0e25b3fdd1a" + integrity sha512-EGHMsNCt+PyStm3o1JWujaTA+HKcTxuEXdSHBBFDavzsgOF13bxTf1LpDYgTZJT3K9TSMP983IwfckP5t66pDw== + dependencies: + "@babel/runtime" "^7.16.0" + "@wordpress/html-entities@^2.11.2": version "2.11.2" resolved "https://registry.yarnpkg.com/@wordpress/html-entities/-/html-entities-2.11.2.tgz#c0e757ee7369239e2a885f7db7e78b81b79f8963" @@ -7433,6 +7548,18 @@ sprintf-js "^1.1.1" tannin "^1.2.0" +"@wordpress/i18n@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/i18n/-/i18n-5.7.0.tgz#fce292bed35c41663bdf9639f1fb4926e8ca3840" + integrity sha512-o1cq1zutE5rMAM//Ra1hRfgHuWNBxFtd7XNk+BuAcILRENMaEoqAoGBmGj9lRtOcqAj+cdoWxFjBIxRa67vIrg== + dependencies: + "@babel/runtime" "^7.16.0" + "@wordpress/hooks" "^4.7.0" + gettext-parser "^1.3.1" + memize "^2.1.0" + sprintf-js "^1.1.1" + tannin "^1.2.0" + "@wordpress/icons@^2.10.3": version "2.10.3" resolved "https://registry.yarnpkg.com/@wordpress/icons/-/icons-2.10.3.tgz#56253dd0119794c600c923cb2255778db66b97f3" @@ -7495,6 +7622,13 @@ dependencies: "@babel/runtime" "^7.16.0" +"@wordpress/is-shallow-equal@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/is-shallow-equal/-/is-shallow-equal-5.7.0.tgz#ac7e29811e8926eec93c5547d04ddcf21b584ac5" + integrity sha512-PW+OEkojwd8pZs7m8m9jVwVhLTA1kxmf01f0R2aC+bGfYvw0mlqcviCQTR6+EpRYpceh2nkDch2mD/LWT8c7ZA== + dependencies: + "@babel/runtime" "^7.16.0" + "@wordpress/jest-console@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@wordpress/jest-console/-/jest-console-5.0.1.tgz#8f2dff67980c75523b8231ac951cfe97a7b907e6" @@ -7597,6 +7731,14 @@ "@wordpress/i18n" "^4.45.0" change-case "^4.1.2" +"@wordpress/keycodes@^4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/keycodes/-/keycodes-4.7.0.tgz#a63ea09597a04f2f0714b5dd3802af2e53f28917" + integrity sha512-x8I0xjRM8U0RnpFHWN9mA+x3MqjhJNBldiCpb59GTi3BIzPeDPgxbosAsAAgF0pYdDtGyiRkrOZA23NTia63TA== + dependencies: + "@babel/runtime" "^7.16.0" + "@wordpress/i18n" "^5.7.0" + "@wordpress/notices@^2.13.3": version "2.13.3" resolved "https://registry.yarnpkg.com/@wordpress/notices/-/notices-2.13.3.tgz#1fc62ec581245275773b9f4f06d7dd5dc7e2e447" @@ -7702,6 +7844,21 @@ dependencies: "@babel/runtime" "^7.16.0" +"@wordpress/priority-queue@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/priority-queue/-/priority-queue-3.7.0.tgz#fbdc114787af7c430b75d2ccb8296e5d5b3d5d9f" + integrity sha512-WgKOhaQdaEeOxRLL49cp2YKfsZyUsR1qHoLid64Jux9FjFqLT8t52UTYJ796AhU4W0ifxf3R1SkNpW5zslxKOg== + dependencies: + "@babel/runtime" "^7.16.0" + requestidlecallback "^0.3.0" + +"@wordpress/private-apis@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/private-apis/-/private-apis-1.7.0.tgz#a1d2b20f3e64177563b34fb05d46f1bf14c69f12" + integrity sha512-H6bbWZRL7u2awmK14ZCz7OupeIjz1HxSlB785X53k9JZ5KsbSK/FCzAvOJ5vCU9poC1fa6IT33qkgx3JNX3JEA== + dependencies: + "@babel/runtime" "^7.16.0" + "@wordpress/redux-routine@^3.14.2": version "3.14.2" resolved "https://registry.yarnpkg.com/@wordpress/redux-routine/-/redux-routine-3.14.2.tgz#f49ce2e66eecb5bdaef86a1f90b4cc4137d5acf4" @@ -7742,6 +7899,16 @@ lodash "^4.17.21" rungen "^0.3.2" +"@wordpress/redux-routine@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/redux-routine/-/redux-routine-5.7.0.tgz#eec9fb6758ac2197407311597ab8a21aa40d2963" + integrity sha512-KOU1+qFEDrptLY6lOQ3pTR+MZwe35dHfCp8xJHUJa/RI9jKULvXWrEIX0qhEMNWlinyQmwdTfmZqKxP8RuFzag== + dependencies: + "@babel/runtime" "^7.16.0" + is-plain-object "^5.0.0" + is-promise "^4.0.0" + rungen "^0.3.2" + "@wordpress/rich-text@^3.25.3": version "3.25.3" resolved "https://registry.yarnpkg.com/@wordpress/rich-text/-/rich-text-3.25.3.tgz#af8533cbaa1c1313b4c6fa9667212a7b1c52016f" @@ -7777,6 +7944,22 @@ memize "^1.1.0" rememo "^4.0.0" +"@wordpress/rich-text@^7.0.2": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/rich-text/-/rich-text-7.7.0.tgz#578098777cf9520a1a5d70eb8038a260cf43b8aa" + integrity sha512-is96sOolYVeE/58jUr6GxZKY1XGWrF988lT8FUg7U4u0KgdDSIEPLacs4USE8OoqxZYCIAnwSPenMXN+ZPvOfw== + dependencies: + "@babel/runtime" "^7.16.0" + "@wordpress/a11y" "^4.7.0" + "@wordpress/compose" "^7.7.0" + "@wordpress/data" "^10.7.0" + "@wordpress/deprecated" "^4.7.0" + "@wordpress/element" "^6.7.0" + "@wordpress/escape-html" "^3.7.0" + "@wordpress/i18n" "^5.7.0" + "@wordpress/keycodes" "^4.7.0" + memize "^2.1.0" + "@wordpress/scripts@^26.16.0": version "26.16.0" resolved "https://registry.yarnpkg.com/@wordpress/scripts/-/scripts-26.16.0.tgz#e0baf60eb11d5d39a85ba6ac5eeb2ac6ee58522b" @@ -7892,6 +8075,14 @@ "@babel/runtime" "^7.13.10" lodash "^4.17.19" +"@wordpress/undo-manager@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/undo-manager/-/undo-manager-1.7.0.tgz#608ab321df7f347c93b923a5eb879f2cc2851512" + integrity sha512-FHkwMD/jbe5jhVXfD9bSNhEivhMeszm20/ymEP6vAsLVJB2K25iAMOGvsq5jtznyJiqQzNUmvPERN0IKnaHQnA== + dependencies: + "@babel/runtime" "^7.16.0" + "@wordpress/is-shallow-equal" "^5.7.0" + "@wordpress/url@^2.22.2": version "2.22.2" resolved "https://registry.yarnpkg.com/@wordpress/url/-/url-2.22.2.tgz#e4267befa6d421b31b40e6e8ff9c973468a6e947" @@ -10644,7 +10835,7 @@ client-only@^0.0.1: resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== -clipboard@^2.0.1: +clipboard@^2.0.1, clipboard@^2.0.11: version "2.0.11" resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.11.tgz#62180360b97dd668b6b3a84ec226975762a70be5" integrity sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw== @@ -12291,7 +12482,7 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== -deepmerge@^4.3.1: +deepmerge@^4.3.0, deepmerge@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== @@ -24525,6 +24716,14 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-dom@^18.3.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.2" + react-element-to-jsx-string@^15.0.0: version "15.0.0" resolved "https://registry.yarnpkg.com/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz#1cafd5b6ad41946ffc8755e254da3fc752a01ac6" @@ -24890,6 +25089,13 @@ react@^18.2.0: dependencies: loose-envify "^1.1.0" +react@^18.3.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== + dependencies: + loose-envify "^1.1.0" + read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" @@ -25328,7 +25534,7 @@ rememo@^3.0.0: resolved "https://registry.yarnpkg.com/rememo/-/rememo-3.0.0.tgz#06e8e76e108865cc1e9b73329db49f844eaf8392" integrity sha512-eWtut/7pqMRnSccbexb647iPjN7ir6Tmf4RG92ZVlykFEkHqGYy9tWnpHH3I+FS+WQ6lQ1i1iDgarYzGKgTcRQ== -rememo@^4.0.0: +rememo@^4.0.0, rememo@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/rememo/-/rememo-4.0.2.tgz#8af1f09fd3bf5809ca0bfd0b803926c67ead8c1e" integrity sha512-NVfSP9NstE3QPNs/TnegQY0vnJnstKQSpcrsI2kBTB3dB2PkdfKdTa+abbjMIDqpc63fE5LfjLgfMst0ULMFxQ== @@ -25924,6 +26130,13 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" +scheduler@^0.23.2: + version "0.23.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== + dependencies: + loose-envify "^1.1.0" + schema-utils@^2.6.5: version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" From 61eae8e29ad6a11d836e32184d7dcabcefd4bdfd Mon Sep 17 00:00:00 2001 From: marinakoleva <marina@yoast.com> Date: Tue, 10 Sep 2024 15:13:51 +0200 Subject: [PATCH 081/313] adjusting fulltexttests after changes to transition words lists --- .../testTexts/en/englishPaperForPerformanceTest.js | 2 +- .../fullTextTests/testTexts/en/englishPaper1.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/yoastseo/spec/fullTextTests/testTexts/en/englishPaperForPerformanceTest.js b/packages/yoastseo/spec/fullTextTests/testTexts/en/englishPaperForPerformanceTest.js index 48bb33fc8ef..cbc8b7b174e 100644 --- a/packages/yoastseo/spec/fullTextTests/testTexts/en/englishPaperForPerformanceTest.js +++ b/packages/yoastseo/spec/fullTextTests/testTexts/en/englishPaperForPerformanceTest.js @@ -127,7 +127,7 @@ const expectedResults = { textTransitionWords: { isApplicable: true, score: 3, - resultText: "<a href='https://yoa.st/34z' target='_blank'>Transition words</a>: Only 5.5% of the sentences contain transition words, " + + resultText: "<a href='https://yoa.st/34z' target='_blank'>Transition words</a>: Only 6.8% of the sentences contain transition words, " + "which is not enough. <a href='https://yoa.st/35a' target='_blank'>Use more of them</a>.", }, passiveVoice: { diff --git a/packages/yoastseo/spec/scoring/assessors/collectionPages/fullTextTests/testTexts/en/englishPaper1.js b/packages/yoastseo/spec/scoring/assessors/collectionPages/fullTextTests/testTexts/en/englishPaper1.js index 4a4046106d3..6d2ad1c18ac 100644 --- a/packages/yoastseo/spec/scoring/assessors/collectionPages/fullTextTests/testTexts/en/englishPaper1.js +++ b/packages/yoastseo/spec/scoring/assessors/collectionPages/fullTextTests/testTexts/en/englishPaper1.js @@ -99,9 +99,8 @@ const expectedResults = { }, textTransitionWords: { isApplicable: true, - score: 6, - resultText: "<a href='https://yoa.st/shopify44' target='_blank'>Transition words</a>: Only 25% of the sentences contain " + - "transition words, which is not enough. <a href='https://yoa.st/shopify45' target='_blank'>Use more of them</a>.", + score: 9, + resultText: "<a href='https://yoa.st/shopify44' target='_blank'>Transition words</a>: Well done!", }, passiveVoice: { isApplicable: true, From 59c7437e3104f39ed0315ce636f4c65d4c9648df Mon Sep 17 00:00:00 2001 From: Thijs van der heijden <thijsvanderheijden2@gmail.com> Date: Tue, 10 Sep 2024 16:32:03 +0200 Subject: [PATCH 082/313] Ui library storybook. --- packages/js/src/dashboard/components/index.js | 1 - .../ui-library/src/components/sidebar-navigation/stories.js | 5 ++++- .../src/components/sidebar-navigation/submenu-item.js | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/js/src/dashboard/components/index.js b/packages/js/src/dashboard/components/index.js index cd3070b84b2..d45a7ef5a64 100644 --- a/packages/js/src/dashboard/components/index.js +++ b/packages/js/src/dashboard/components/index.js @@ -1,2 +1 @@ export { default as RouteLayout } from "./route-layout"; -export { default as SidebarNavigation } from "./sidebar-navigation"; diff --git a/packages/ui-library/src/components/sidebar-navigation/stories.js b/packages/ui-library/src/components/sidebar-navigation/stories.js index 37849481424..5b3369dfce9 100644 --- a/packages/ui-library/src/components/sidebar-navigation/stories.js +++ b/packages/ui-library/src/components/sidebar-navigation/stories.js @@ -47,6 +47,9 @@ export const Factory = { <SidebarNavigation.SubmenuItem to="#sub3" label="SubmenuItem 3 label" /> </SidebarNavigation.MenuItem> + <ul className="yst-mt-1 yst-space-y-1"> + <SidebarNavigation.SubmenuItem icon={ NewspaperIcon } to="#sub3" label="SubmenuItem 3 label" /> + </ul> </SidebarNavigation.Sidebar>, }, }; @@ -217,7 +220,7 @@ export default { }, icon: { control: "object", - description: "Available for `MenuItem`", + description: "Available for `MenuItem` and `SubmenuItem`", table: { type: { summary: "JSX Element" }, }, diff --git a/packages/ui-library/src/components/sidebar-navigation/submenu-item.js b/packages/ui-library/src/components/sidebar-navigation/submenu-item.js index 35dbacb933d..695db256ee1 100644 --- a/packages/ui-library/src/components/sidebar-navigation/submenu-item.js +++ b/packages/ui-library/src/components/sidebar-navigation/submenu-item.js @@ -7,6 +7,7 @@ import { useNavigationContext } from "./index"; * @param {JSX.node} label The label. * @param {JSX.ElementClass} [as] The field component. * @param {string} [pathProp] The key of the path in the props. Defaults to `href`. + * @param {JSX.ElementClass} [icon] The Icon component. * @param {Object} [props] Extra props. * @returns {JSX.Element} The submenu item element. */ @@ -44,6 +45,7 @@ SubmenuItem.propTypes = { pathProp: PropTypes.string, label: PropTypes.node.isRequired, isActive: PropTypes.bool, + icon: PropTypes.elementType, }; export default SubmenuItem; From 3b2db9d61b1affeefcc9dc470ef45ff2d7f05970 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden <thijsvanderheijden2@gmail.com> Date: Tue, 10 Sep 2024 17:36:08 +0200 Subject: [PATCH 083/313] cs --- admin/class-config.php | 1 - 1 file changed, 1 deletion(-) diff --git a/admin/class-config.php b/admin/class-config.php index 1c2490df21c..1a476084ddd 100644 --- a/admin/class-config.php +++ b/admin/class-config.php @@ -6,7 +6,6 @@ */ use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; -use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Dashboard\User_Interface\General_Page_Integration; use Yoast\WP\SEO\Integrations\Academy_Integration; use Yoast\WP\SEO\Integrations\Settings_Integration; From 477b64809b6227acedbf2a29a5bb9c1ad995d7c7 Mon Sep 17 00:00:00 2001 From: Vraja Das <vraja.pro@gmail.com> Date: Tue, 10 Sep 2024 18:36:16 +0300 Subject: [PATCH 084/313] refactor: add woo seo to integrations in editors --- .../integrations/woocommerce-seo.php | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/editors/framework/integrations/woocommerce-seo.php diff --git a/src/editors/framework/integrations/woocommerce-seo.php b/src/editors/framework/integrations/woocommerce-seo.php new file mode 100644 index 00000000000..6f4557e2453 --- /dev/null +++ b/src/editors/framework/integrations/woocommerce-seo.php @@ -0,0 +1,53 @@ +<?php +// @phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- This namespace should reflect the namespace of the original class. +namespace Yoast\WP\SEO\Editors\Framework\Integrations; + +use WPSEO_Addon_Manager; +use Yoast\WP\SEO\Editors\Domain\Integrations\Integration_Data_Provider_Interface; + +/** + * Describes if the Woocommerce SEO addon is enabled. + */ +class WooCommerceSEO implements Integration_Data_Provider_Interface { + + /** + * The addon manager. + * + * @var WPSEO_Addon_Manager + */ + private $addon_manager; + + /** + * The constructor. + */ + public function __construct() { + $this->addon_manager = new WPSEO_Addon_Manager(); + } + + /** + * If the plugin is activated. + * + * @return bool If the plugin is activated. + */ + public function is_enabled(): bool { + return \is_plugin_active( $this->addon_manager->get_plugin_file( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) ); + } + + /** + * Return this object represented by a key value array. + * + * @return array<string,bool> Returns the name and if the addon is enabled. + */ + public function to_array(): array { + return [ 'isWooCommerceSeoActive' => $this->is_enabled() ]; + } + + /** + * Returns this object represented by a key value structure that is compliant with the script data array. + * + * @return array<string,bool> Returns the legacy key and if the feature is enabled. + */ + public function to_legacy_array(): array { + return [ 'isWooCommerceSeoActive' => $this->is_enabled() ]; + } +} From f86c3a980ac7b756aa652d11061b543ae1850b22 Mon Sep 17 00:00:00 2001 From: Vraja Das <vraja.pro@gmail.com> Date: Tue, 10 Sep 2024 18:37:14 +0300 Subject: [PATCH 085/313] refactor: remove isWooSeo from metabox class --- admin/metabox/class-metabox.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/admin/metabox/class-metabox.php b/admin/metabox/class-metabox.php index d9235bae6b9..2deba2218c2 100644 --- a/admin/metabox/class-metabox.php +++ b/admin/metabox/class-metabox.php @@ -881,9 +881,6 @@ public function enqueue() { 'log_level' => WPSEO_Utils::get_analysis_worker_log_level(), ]; - $addon_manager = new WPSEO_Addon_Manager(); - $woocommerce_seo_active = is_plugin_active( $addon_manager->get_plugin_file( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) ); - $script_data = [ 'metabox' => $this->get_metabox_script_data(), 'userLanguageCode' => WPSEO_Language_Utils::get_language( get_user_locale() ), @@ -899,7 +896,6 @@ public function enqueue() { ], 'isJetpackBoostActive' => ( $is_block_editor ) ? YoastSEO()->classes->get( Jetpack_Boost_Active_Conditional::class )->is_met() : false, 'isJetpackBoostNotPremium' => ( $is_block_editor ) ? YoastSEO()->classes->get( Jetpack_Boost_Not_Premium_Conditional::class )->is_met() : false, - 'isWooCommerceSeoActive' => $woocommerce_seo_active, ]; /** From 7ec8886bf5dd65faeed9cc7221f2ac1947c4b678 Mon Sep 17 00:00:00 2001 From: Vraja Das <vraja.pro@gmail.com> Date: Tue, 10 Sep 2024 18:38:20 +0300 Subject: [PATCH 086/313] refactor: move woocommerce check to preference store --- packages/js/src/redux/reducers/preferences.js | 2 ++ packages/js/src/redux/selectors/index.js | 1 - packages/js/src/redux/selectors/isWooSEO.js | 30 ------------------- .../js/src/redux/selectors/preferences.js | 15 ++++++++++ 4 files changed, 17 insertions(+), 31 deletions(-) delete mode 100644 packages/js/src/redux/selectors/isWooSEO.js diff --git a/packages/js/src/redux/reducers/preferences.js b/packages/js/src/redux/reducers/preferences.js index 207303bbb76..58a3bef68bf 100644 --- a/packages/js/src/redux/reducers/preferences.js +++ b/packages/js/src/redux/reducers/preferences.js @@ -34,6 +34,8 @@ function getDefaultState() { isInsightsEnabled: get( window, "wpseoScriptData.metabox.isInsightsEnabled", false ), isNewsEnabled: ! ! window.wpseoAdminL10n.news_seo_is_active, isAiFeatureActive: Boolean( window.wpseoAdminL10n.isAiFeatureActive ), + isWooCommerceSeoActive: get( window, "wpseoScriptData.metabox.isWooCommerceSeoActive", false ), + isWooCommerceActive: get( window, "wpseoScriptData.metabox.isWooCommerceActive", false ), }; } diff --git a/packages/js/src/redux/selectors/index.js b/packages/js/src/redux/selectors/index.js index cc28af23395..fdd18fdb0b5 100644 --- a/packages/js/src/redux/selectors/index.js +++ b/packages/js/src/redux/selectors/index.js @@ -39,5 +39,4 @@ export * from "./WincherModal"; export * from "./WincherRequest"; export * from "./WincherSEOPerformance"; export * from "./isPremium"; -export * from "./isWooSEO"; export * from "./postId"; diff --git a/packages/js/src/redux/selectors/isWooSEO.js b/packages/js/src/redux/selectors/isWooSEO.js deleted file mode 100644 index 9b5113023bf..00000000000 --- a/packages/js/src/redux/selectors/isWooSEO.js +++ /dev/null @@ -1,30 +0,0 @@ -import { get } from "lodash"; -import { getIsProduct } from "./editorContext"; - -/** - * Determines whether the WooCommerce SEO addon is active. - * - * @returns {Boolean} Whether the plugin is WooCommerce SEO or not. - */ -export const getIsWooSeoActive = () => Boolean( get( window, "wpseoScriptData.isWooCommerceSeoActive", false ) ); - -/** - * Checks if WooCommerce is active. - * - * @returns {boolean} True if WooCommerce is active. - */ -export const getIsWooCommerceActive = () => get( window, "wpseoScriptData.metabox.isWooCommerceActive", false ); - -/** - * Determines whether the WooCommerce SEO addon is not active in a product page. - * - * @param {Object} state The state. - * @returns {Boolean} Whether the plugin is WooCommerce SEO or not. - */ -export const getIsWooSeoUpsell = ( state ) => { - const isWooSeoActive = getIsWooSeoActive(); - const isWooCommerceActive = getIsWooCommerceActive(); - const isProductPage = getIsProduct( state ); - - return ! isWooSeoActive && isWooCommerceActive && isProductPage; -}; diff --git a/packages/js/src/redux/selectors/preferences.js b/packages/js/src/redux/selectors/preferences.js index c1ad9001d19..54b83b7f675 100644 --- a/packages/js/src/redux/selectors/preferences.js +++ b/packages/js/src/redux/selectors/preferences.js @@ -1,4 +1,5 @@ import { get } from "lodash"; +import { getIsProduct } from "./editorContext"; /** * Gets a preference. @@ -28,3 +29,17 @@ export const getPreferences = state => state.preferences; * @returns {boolean} The isKeywordAnalysisActive. */ export const getIsKeywordAnalysisActive = state => get( state, "preferences.isKeywordAnalysisActive", false ); + +/** + * Determines whether the WooCommerce SEO addon is not active in a product page. + * + * @param {Object} state The state. + * @returns {Boolean} Whether the plugin is WooCommerce SEO or not. + */ +export const getIsWooSeoUpsell = ( state ) => { + const isWooSeoActive = getPreference( state, "isWooSeoActive" ); + const isWooCommerceActive = getPreference( state, "isWooCommerceActive" ); + const isProductPage = getIsProduct( state ); + + return ! isWooSeoActive && isWooCommerceActive && isProductPage; +}; From 4ef3aa93657925fd18bcd40bb86848f1a5e4d3b9 Mon Sep 17 00:00:00 2001 From: Vraja Das <vraja.pro@gmail.com> Date: Tue, 10 Sep 2024 18:39:06 +0300 Subject: [PATCH 087/313] refactor: implement preference selectors --- packages/js/src/components/SchemaTab.js | 2 +- packages/js/src/components/fills/MetaboxFill.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js/src/components/SchemaTab.js b/packages/js/src/components/SchemaTab.js index 4d2c2c5cf24..dfd4b0ff75a 100644 --- a/packages/js/src/components/SchemaTab.js +++ b/packages/js/src/components/SchemaTab.js @@ -69,7 +69,7 @@ NewsAlert.propTypes = { */ const getSchemaTypeOptions = ( schemaTypeOptions, defaultType, postTypeName ) => { const isProduct = useSelect( ( select ) => select( STORE ).getIsProduct(), [] ); - const isWooSeoActive = useSelect( select => select( STORE ).getIsWooSeoActive(), [] ); + const isWooSeoActive = useSelect( select => select( STORE ).getPreference( "isWooCommerceSeoActive" ), [] ); const disablePageTypeSelect = isProduct && isWooSeoActive; const schemaOption = disablePageTypeSelect ? { name: __( "Item Page", "wordpress-seo" ), value: "ItemPage" } : schemaTypeOptions.find( option => option.value === defaultType ); return [ diff --git a/packages/js/src/components/fills/MetaboxFill.js b/packages/js/src/components/fills/MetaboxFill.js index 95197220fae..31e110d77ab 100644 --- a/packages/js/src/components/fills/MetaboxFill.js +++ b/packages/js/src/components/fills/MetaboxFill.js @@ -39,7 +39,7 @@ const BlackFridayPromotionWithMetaboxWarningsCheck = withMetaboxWarningsCheck( B export default function MetaboxFill( { settings } ) { const isTerm = useSelect( ( select ) => select( "yoast-seo/editor" ).getIsTerm(), [] ); const isProduct = useSelect( ( select ) => select( "yoast-seo/editor" ).getIsProduct(), [] ); - const isWooCommerceActive = useSelect( ( select ) => select( "yoast-seo/editor" ).getIsWooCommerceActive(), [] ); + const isWooCommerceActive = useSelect( ( select ) => select( "yoast-seo/editor" ).getPreference( "isWooCommerceActive" ), [] ); const shouldShowWooCommerceChecklistPromo = isProduct && isWooCommerceActive; From cf66a163f300f806d85189f32d72a1bf9f29368a Mon Sep 17 00:00:00 2001 From: Vraja Das <vraja.pro@gmail.com> Date: Wed, 11 Sep 2024 08:24:50 +0300 Subject: [PATCH 088/313] refactor: fix class name --- src/editors/framework/integrations/woocommerce-seo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editors/framework/integrations/woocommerce-seo.php b/src/editors/framework/integrations/woocommerce-seo.php index 6f4557e2453..5be8ea5625d 100644 --- a/src/editors/framework/integrations/woocommerce-seo.php +++ b/src/editors/framework/integrations/woocommerce-seo.php @@ -8,7 +8,7 @@ /** * Describes if the Woocommerce SEO addon is enabled. */ -class WooCommerceSEO implements Integration_Data_Provider_Interface { +class WooCommerce_SEO implements Integration_Data_Provider_Interface { /** * The addon manager. From c32de0027b7c427c3270396a97f26ef8a0df0e76 Mon Sep 17 00:00:00 2001 From: Vraja Das <vraja.pro@gmail.com> Date: Wed, 11 Sep 2024 08:37:12 +0300 Subject: [PATCH 089/313] implement getPreference selector --- packages/js/src/components/SchemaTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/src/components/SchemaTab.js b/packages/js/src/components/SchemaTab.js index dfd4b0ff75a..2db8c4abb12 100644 --- a/packages/js/src/components/SchemaTab.js +++ b/packages/js/src/components/SchemaTab.js @@ -171,7 +171,7 @@ const Content = ( props ) => { const [ focusedArticleType, setFocusedArticleType ] = useState( props.schemaArticleTypeSelected ); const woocommerceUpsellText = __( "Want your products stand out in search results with rich results like price, reviews and more?", "wordpress-seo" ); const isProduct = useSelect( ( select ) => select( STORE ).getIsProduct(), [] ); - const isWooSeoActive = useSelect( select => select( STORE ).getIsWooSeoActive(), [] ); + const isWooSeoActive = useSelect( select => select( STORE ).getPreference( "isWooCommerceSeoActive" ), [] ); const settingsLink = useSelect( select => select( STORE ).selectAdminLink( "?page=wpseo_page_settings" ), [] ); const disablePageTypeSelect = isProduct && isWooSeoActive; From 9e207057824a9e7e6ca30b5c6dc36ac14a0c5af3 Mon Sep 17 00:00:00 2001 From: Vraja Das <vraja.pro@gmail.com> Date: Mon, 9 Sep 2024 09:49:52 +0300 Subject: [PATCH 090/313] remove jetpack boost properties from wpseoScriptData --- admin/metabox/class-metabox.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/admin/metabox/class-metabox.php b/admin/metabox/class-metabox.php index d9235bae6b9..c875ea3d1fa 100644 --- a/admin/metabox/class-metabox.php +++ b/admin/metabox/class-metabox.php @@ -5,8 +5,6 @@ * @package WPSEO\Admin */ -use Yoast\WP\SEO\Conditionals\Third_Party\Jetpack_Boost_Active_Conditional; -use Yoast\WP\SEO\Conditionals\Third_Party\Jetpack_Boost_Not_Premium_Conditional; use Yoast\WP\SEO\Editors\Application\Site\Website_Information_Repository; use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter; use Yoast\WP\SEO\Presenters\Admin\Meta_Fields_Presenter; @@ -897,8 +895,6 @@ public function enqueue() { 'plugins' => $plugins_script_data, 'worker' => $worker_script_data, ], - 'isJetpackBoostActive' => ( $is_block_editor ) ? YoastSEO()->classes->get( Jetpack_Boost_Active_Conditional::class )->is_met() : false, - 'isJetpackBoostNotPremium' => ( $is_block_editor ) ? YoastSEO()->classes->get( Jetpack_Boost_Not_Premium_Conditional::class )->is_met() : false, 'isWooCommerceSeoActive' => $woocommerce_seo_active, ]; From 5dcfacf6ee3e6723219dca3fd83219bc2c8f7eac Mon Sep 17 00:00:00 2001 From: Vraja Das <vraja.pro@gmail.com> Date: Mon, 9 Sep 2024 09:51:58 +0300 Subject: [PATCH 091/313] removes jetpack boost ad component --- packages/js/src/components/JetpackBoost.js | 123 --------------------- 1 file changed, 123 deletions(-) delete mode 100644 packages/js/src/components/JetpackBoost.js diff --git a/packages/js/src/components/JetpackBoost.js b/packages/js/src/components/JetpackBoost.js deleted file mode 100644 index 37458ddaf98..00000000000 --- a/packages/js/src/components/JetpackBoost.js +++ /dev/null @@ -1,123 +0,0 @@ -import { ExternalLinkIcon, XIcon } from "@heroicons/react/outline"; -import { useSelect } from "@wordpress/data"; -import { PluginPrePublishPanel } from "@wordpress/edit-post"; -import { __, sprintf } from "@wordpress/i18n"; -import { Link, Root, Title } from "@yoast/ui-library"; -import { get } from "lodash"; -import PropTypes from "prop-types"; -import { ReactComponent as JetpackBoostLogo } from "../../images/jetpack-boost-logo.svg"; -import { ReactComponent as YoastLogo } from "../../images/yoast-seo-simple-logo.svg"; -import withPersistentDismiss from "../containers/withPersistentDismiss"; - -/** - * The JetpackBoost ad description. - * Used to avoid eslint complexity warning. - * @param {boolean} isJetpackBoostActive The component props. - * @returns {string} The JetpackBoost ad description. - */ -const getDescription = ( isJetpackBoostActive ) => { - return sprintf( - isJetpackBoostActive - /* translators: 1: Yoast, 2: Jetpack Boost, 3: Boost (short for Jetpack Boost). */ - ? __( "%1$s recommends using %2$s for automated Critical CSS generation. Whenever you change your site, %3$s will automatically generate optimal CSS and performance scores. Upgrade %3$s, speed up your site, and improve its ranking!", "wordpress-seo" ) - /* translators: 1: Yoast, 2: Jetpack Boost, 3: Boost (short for Jetpack Boost). */ - : __( "%1$s recommends using %2$s to speed up your site. Optimize CSS, defer non-essential Javascript, and lazy load images. Install %3$s, speed up your site, and improve its ranking!", "wordpress-seo" ), - "Yoast", - "Jetpack Boost", - "Boost" - ); -}; - -/* eslint-disable complexity */ -/** - * @param {string} store The redux store key. - * @param {boolean} isAlertDismissed Whether the "alert" is dismissed. - * @param {function} onDismissed Function that will dismiss the "alert". - * @returns {JSX.Element} The JetpackBoost element (which is null when dismissed). -*/ -const JetpackBoost = ( { store, isAlertDismissed, onDismissed } ) => { - const host = window.location.host; - const isJetpackBoostActive = get( window, "wpseoScriptData.isJetpackBoostActive", "" ) === "1"; - const isJetpackBoostNotPremium = get( window, "wpseoScriptData.isJetpackBoostNotPremium", "" ) === "1"; - const getJetpackBoostPrePublishLink = useSelect( select => select( store ).selectLink( `https://yoa.st/jetpack-boost-get-prepublish?domain=${host}`, [] ) ); - const upgradeJetpackBoostPrePublishLink = useSelect( select => select( store ).selectLink( `https://yoa.st/jetpack-boost-upgrade-prepublish?domain=${host}`, [] ) ); - - const isPremium = useSelect( select => select( store ).getIsPremium() ); - if ( isPremium || isAlertDismissed || ! isJetpackBoostNotPremium ) { - return null; - } - - return ( - <PluginPrePublishPanel - className="yoast-seo-sidebar-panel" - initialOpen={ true } - > - { /* If this UI library Root is available up the chain, there is no need for it here anymore. */ } - <Root className="yst-root yst-relative"> - <div - className="yst-absolute yst-top-0 yst-bottom-0 yst--m-[16px] yst-w-[3px] yst-bg-primary-500" - /> - <div className="yst-flex"> - <YoastLogo className="yst-h-5 yst-w-5 yst-text-primary-500 yst-bg-white yst-z-10 yst-border-r yst-border-white" /> - <JetpackBoostLogo className="yst-h-5 yst-w-5 yst--ml-0.5" /> - <button type="button" className="yst-ml-auto" onClick={ onDismissed }> - <XIcon className="yst-h-4 yst-w-4 yst-text-gray-400" /> - <span className="yst-sr-only"> - { - /* translators: Hidden accessibility text. */ - __( "Dismiss get Jetpack Boost", "wordpress-seo" ) - } - </span> - </button> - </div> - <Title as="h2" size="3" className="yst-mt-3 yst-text-sm yst-leading-normal yst-font-semibold"> - { sprintf( - /* translators: 1: Yoast SEO; 2: Jetpack Boost. */ - __( "Speed up your website with %1$s and %2$s", "wordpress-seo" ), - "Yoast SEO", - "Jetpack Boost" - ) } - -

- { getDescription( isJetpackBoostActive ) } -

- - - - { sprintf( - isJetpackBoostActive - /* translators: Jetpack Boost. */ - ? __( "Upgrade %s", "wordpress-seo" ) - /* translators: Jetpack Boost. */ - : __( "Get %s", "wordpress-seo" ), - "Jetpack Boost" - ) } - - - - { - /* translators: Hidden accessibility text. */ - __( "(Opens in a new browser tab)", "wordpress-seo" ) - } - - - - - ); -}; -/* eslint-enable complexity */ - -JetpackBoost.propTypes = { - store: PropTypes.string, - // eslint-disable-next-line react/no-unused-prop-types -- Used in the `withPersistentDismiss` HOC. - alertKey: PropTypes.string, - isAlertDismissed: PropTypes.bool.isRequired, - onDismissed: PropTypes.func.isRequired, -}; - -JetpackBoost.defaultProps = { - store: "yoast-seo/editor", - alertKey: "get-jetpack-boost-pre-publish-notification", -}; - -export default withPersistentDismiss( JetpackBoost ); From 438771c70fc73dffd495653f29be2d436a5eb0ec Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 9 Sep 2024 09:55:26 +0300 Subject: [PATCH 092/313] removes logos used in the ad --- packages/js/images/jetpack-boost-logo.svg | 1 - packages/js/images/yoast-seo-simple-logo.svg | 1 - 2 files changed, 2 deletions(-) delete mode 100644 packages/js/images/jetpack-boost-logo.svg delete mode 100644 packages/js/images/yoast-seo-simple-logo.svg diff --git a/packages/js/images/jetpack-boost-logo.svg b/packages/js/images/jetpack-boost-logo.svg deleted file mode 100644 index d670bec7659..00000000000 --- a/packages/js/images/jetpack-boost-logo.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/js/images/yoast-seo-simple-logo.svg b/packages/js/images/yoast-seo-simple-logo.svg deleted file mode 100644 index 5fa9051d0a5..00000000000 --- a/packages/js/images/yoast-seo-simple-logo.svg +++ /dev/null @@ -1 +0,0 @@ - From e3494171ea5a46b4e705ce6d615b671b0bc6bfef Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 9 Sep 2024 10:35:13 +0300 Subject: [PATCH 093/313] removes pre-publish jetpack boost dismissed alert class --- .../alerts/jetpack-boost-pre-publish.php | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 src/integrations/alerts/jetpack-boost-pre-publish.php diff --git a/src/integrations/alerts/jetpack-boost-pre-publish.php b/src/integrations/alerts/jetpack-boost-pre-publish.php deleted file mode 100644 index 9709a8b0b9c..00000000000 --- a/src/integrations/alerts/jetpack-boost-pre-publish.php +++ /dev/null @@ -1,26 +0,0 @@ - Date: Mon, 9 Sep 2024 10:38:32 +0300 Subject: [PATCH 094/313] removes jetpack from integration page class --- src/integrations/admin/integrations-page.php | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/integrations/admin/integrations-page.php b/src/integrations/admin/integrations-page.php index 5fec0c8a03a..7d0d296a111 100644 --- a/src/integrations/admin/integrations-page.php +++ b/src/integrations/admin/integrations-page.php @@ -12,8 +12,8 @@ use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\Jetpack_Conditional; use Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Activated_Conditional; -use Yoast\WP\SEO\Conditionals\Third_Party\Jetpack_Boost_Active_Conditional; -use Yoast\WP\SEO\Conditionals\Third_Party\Jetpack_Boost_Not_Premium_Conditional; + + use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Woocommerce_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; @@ -117,8 +117,6 @@ public function enqueue_assets() { $elementor_conditional = new Elementor_Activated_Conditional(); $jetpack_conditional = new Jetpack_Conditional(); - $jetpack_boost_active_conditional = new Jetpack_Boost_Active_Conditional(); - $jetpack_boost_not_premium_conditional = new Jetpack_Boost_Not_Premium_Conditional(); $woocommerce_seo_file = 'wpseo-woocommerce/wpseo-woocommerce.php'; $acf_seo_file = 'acf-content-analysis-for-yoast-seo/yoast-acf-analysis.php'; @@ -139,8 +137,6 @@ public function enqueue_assets() { $acf_active = \class_exists( 'acf' ); $algolia_active = \is_plugin_active( $algolia_file ); $edd_active = \class_exists( Easy_Digital_Downloads::class ); - $jetpack_boost_active = $jetpack_boost_active_conditional->is_met(); - $jetpack_boost_premium = ( ! $jetpack_boost_not_premium_conditional->is_met() ); $old_algolia_active = \is_plugin_active( $old_algolia_file ); $tec_active = \class_exists( Events_Schema::class ); $ssp_active = \class_exists( PodcastEpisode::class ); @@ -199,12 +195,6 @@ public function enqueue_assets() { 'mastodon_active' => $mastodon_active, 'is_multisite' => \is_multisite(), 'plugin_url' => \plugins_url( '', \WPSEO_FILE ), - 'jetpack-boost_active' => $jetpack_boost_active, - 'jetpack-boost_premium' => $jetpack_boost_premium, - 'jetpack-boost_logo_link' => WPSEO_Shortlinker::get( 'https://yoa.st/integrations-logo-jetpack-boost' ), - 'jetpack-boost_get_link' => WPSEO_Shortlinker::get( 'https://yoa.st/integrations-get-jetpack-boost?domain=' . $host ), - 'jetpack-boost_upgrade_link' => WPSEO_Shortlinker::get( 'https://yoa.st/integrations-upgrade-jetpack-boost?domain=' . $host ), - 'jetpack-boost_learn_more_link' => \admin_url( 'admin.php?page=jetpack-boost' ), ] ); } From ec87d7e72330e4c65986a2fb313f2c481b34caa6 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 9 Sep 2024 10:38:49 +0300 Subject: [PATCH 095/313] removes jetpack boost conditionals --- .../jetpack-boost-active-conditional.php | 20 ----------- .../jetpack-boost-not-premium-conditional.php | 36 ------------------- 2 files changed, 56 deletions(-) delete mode 100644 src/conditionals/third-party/jetpack-boost-active-conditional.php delete mode 100644 src/conditionals/third-party/jetpack-boost-not-premium-conditional.php diff --git a/src/conditionals/third-party/jetpack-boost-active-conditional.php b/src/conditionals/third-party/jetpack-boost-active-conditional.php deleted file mode 100644 index e96ac83ffd2..00000000000 --- a/src/conditionals/third-party/jetpack-boost-active-conditional.php +++ /dev/null @@ -1,20 +0,0 @@ -is_premium(); - } - - /** - * Retrieves, if available, if Jetpack Boost has priority feature available. - * - * @return bool Whether Jetpack Boost is premium. - */ - private function is_premium() { - if ( \class_exists( '\Automattic\Jetpack_Boost\Lib\Premium_Features', false ) ) { - return Premium_Features::has_feature( - Premium_Features::PRIORITY_SUPPORT - ); - } - - return false; - } -} From f3c27a96151d195621f4912156f232f85de1ad6d Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 9 Sep 2024 10:40:42 +0300 Subject: [PATCH 096/313] removes client side jetpack boost integration code --- .../images/jetpack-boost-integration-logo.svg | 27 --- .../jetpack-boost-integration.js | 173 ------------------ 2 files changed, 200 deletions(-) delete mode 100644 packages/js/images/jetpack-boost-integration-logo.svg delete mode 100644 packages/js/src/integrations-page/jetpack-boost-integration.js diff --git a/packages/js/images/jetpack-boost-integration-logo.svg b/packages/js/images/jetpack-boost-integration-logo.svg deleted file mode 100644 index b00a8b2bd4c..00000000000 --- a/packages/js/images/jetpack-boost-integration-logo.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - Group - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/js/src/integrations-page/jetpack-boost-integration.js b/packages/js/src/integrations-page/jetpack-boost-integration.js deleted file mode 100644 index 53b05deb678..00000000000 --- a/packages/js/src/integrations-page/jetpack-boost-integration.js +++ /dev/null @@ -1,173 +0,0 @@ -import { ArrowSmRightIcon, CheckIcon, XIcon } from "@heroicons/react/solid"; -import { ExclamationIcon } from "@heroicons/react/outline"; -import { ReactComponent as JetpackBoostLogo } from "../../images/jetpack-boost-integration-logo.svg"; -import { createInterpolateElement } from "@wordpress/element"; -import { __, sprintf } from "@wordpress/i18n"; -import { Badge, Link } from "@yoast/ui-library"; -import { Card } from "./tailwind-components/card"; - -/* eslint-disable complexity */ -/** - * An integration for Jetpack Boost. - * - * @returns {WPElement} A card representing an integration for Jetpack Boost. - */ -export const JetpackBoostIntegration = () => { - const IntegrationLogo = JetpackBoostLogo; - - const isJetpackBoostActive = Boolean( window.wpseoIntegrationsData[ "jetpack-boost_active" ] ); - const isJetpackBoostPremium = Boolean( window.wpseoIntegrationsData[ "jetpack-boost_premium" ] ); - - let claim = createInterpolateElement( - sprintf( - /* translators: 1: bold open tag; 2: Yoast SEO; 3: Jetpack Boost; 4: bold close tag. */ - __( "Speed up your site with %1$s%2$s and %3$s%4$s", "wordpress-seo" ), - "", - "Yoast SEO", - "Jetpack Boost", - "" - ), { - strong: , - } - ); - if ( isJetpackBoostActive ) { - claim = createInterpolateElement( - sprintf( - /* translators: 1: bold open tag; 2: Jetpack Boost Premium ; 3: bold close tag. */ - __( "Speed up your site with %1$s%2$s%3$s", "wordpress-seo" ), - "", - "Jetpack Boost Premium", - "" - ), { - strong: , - } - ); - } - - let description = sprintf( - /* translators: 1: Yoast, 2: Jetpack Boost, 3: Boost (short for Jetpack Boost). */ - __( "%1$s recommends using %2$s to speed up your site. Optimize CSS, defer non-essential Javascript, and lazy load images. Install %3$s, speed up your site, and improve its ranking!", "wordpress-seo" ), - "Yoast", - "Jetpack Boost", - "Boost" - ); - if ( isJetpackBoostActive && ! isJetpackBoostPremium ) { - description = sprintf( - /* translators: 1: Yoast, 2: Jetpack Boost, 3: Boost (short for Jetpack Boost). */ - __( "%1$s recommends using %2$s for automated Critical CSS generation. Whenever you change your site, %3$s will automatically regenerate your site’s Critical CSS and performance scores. Upgrade %3$s, speed up your site, and improve its ranking!", "wordpress-seo" ), - "Yoast", - "Jetpack Boost", - "Boost" - ); - } - if ( isJetpackBoostActive && isJetpackBoostPremium ) { - description = sprintf( - /* translators: 1: Yoast, 2: Jetpack Boost, 3: Boost (short for Jetpack Boost). */ - __( "%1$s recommends using %2$s for automated Critical CSS generation. Whenever you change your site, %3$s will automatically regenerate your site’s Critical CSS and performance scores.", "wordpress-seo" ), - "Yoast", - "Jetpack Boost", - "Boost" - ); - } - - let learnMoreLinkText = sprintf( - /* translators: Jetpack Boost. */ - __( "Get %s", "wordpress-seo" ), - "Jetpack Boost" - ); - if ( isJetpackBoostActive ) { - learnMoreLinkText = sprintf( - /* translators: Jetpack Boost. */ - __( "Upgrade %s", "wordpress-seo" ), - "Jetpack Boost" - ); - } - if ( isJetpackBoostPremium ) { - learnMoreLinkText = __( "Learn more", "wordpress-seo" ); - } - - let learnMoreLink = window.wpseoIntegrationsData[ "jetpack-boost_get_link" ]; - if ( isJetpackBoostActive ) { - learnMoreLink = window.wpseoIntegrationsData[ "jetpack-boost_upgrade_link" ]; - } - if ( isJetpackBoostPremium ) { - learnMoreLink = window.wpseoIntegrationsData[ "jetpack-boost_learn_more_link" ]; - } - - return ( - - - - { IntegrationLogo && } - - { - /* translators: Hidden accessibility text. */ - __( "(Opens in a new browser tab)", "wordpress-seo" ) - } - - - { __( "New", "wordpress-seo" ) } - - -
-

- { claim } -

-

{ description } - { learnMoreLink && - { learnMoreLinkText } - - { - /* translators: Hidden accessibility text. */ - __( "(Opens in a new browser tab)", "wordpress-seo" ) - } - - - } -

-
-
- - { ! isJetpackBoostActive &&

- - { __( "Plugin not detected", "wordpress-seo" ) } - - -

} - { isJetpackBoostActive && ! isJetpackBoostPremium &&

- - { __( "Integration active, upgrade available", "wordpress-seo" ) } - - -

} - { isJetpackBoostActive && isJetpackBoostPremium &&

- - { __( "Integration active", "wordpress-seo" ) } - - -

} -
-
- ); -}; From b20f296defb00b98786b21b3babfe7af1bfee6c6 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 9 Sep 2024 10:42:18 +0300 Subject: [PATCH 097/313] removes unused import of a class --- src/integrations/admin/integrations-page.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/integrations/admin/integrations-page.php b/src/integrations/admin/integrations-page.php index 7d0d296a111..9528bead8da 100644 --- a/src/integrations/admin/integrations-page.php +++ b/src/integrations/admin/integrations-page.php @@ -8,7 +8,6 @@ use WP_Recipe_Maker; use WPSEO_Addon_Manager; use WPSEO_Admin_Asset_Manager; -use WPSEO_Shortlinker; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\Jetpack_Conditional; use Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Activated_Conditional; From a836544b132bf84ee5db6db5a7b7768d4bf3de10 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 9 Sep 2024 10:44:42 +0300 Subject: [PATCH 098/313] fix integration page tests --- .../Admin/Integrations_Page_Integration_Test.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/Unit/Integrations/Admin/Integrations_Page_Integration_Test.php b/tests/Unit/Integrations/Admin/Integrations_Page_Integration_Test.php index cefb2846f92..5ebbb5a7531 100644 --- a/tests/Unit/Integrations/Admin/Integrations_Page_Integration_Test.php +++ b/tests/Unit/Integrations/Admin/Integrations_Page_Integration_Test.php @@ -7,7 +7,6 @@ use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; -use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; use Yoast\WP\SEO\Helpers\Woocommerce_Helper; use Yoast\WP\SEO\Integrations\Admin\Integrations_Page; @@ -121,14 +120,11 @@ public function test_enqueue_assets() { Monkey\Functions\expect( 'get_site_url' ) ->andReturn( 'https://www.example.com' ); - $short_link = Mockery::mock( Short_Link_Helper::class ); - $short_link->expects( 'get' )->times( 3 )->andReturn( 'https://www.example.com?some=var' ); $url_helper = Mockery::mock( Url_Helper::class ); $url_helper->expects()->get_url_host( 'https://www.example.com' )->andReturn( 'https://www.example.com' ); $container = $this->create_container_with( [ Url_Helper::class => $url_helper, - Short_Link_Helper::class => $short_link, ] ); @@ -172,13 +168,6 @@ public function test_enqueue_assets() { 'mastodon_active' => false, 'is_multisite' => false, 'plugin_url' => 'https://www.example.com', - - 'jetpack-boost_active' => false, - 'jetpack-boost_premium' => false, - 'jetpack-boost_logo_link' => 'https://www.example.com?some=var', - 'jetpack-boost_get_link' => 'https://www.example.com?some=var', - 'jetpack-boost_upgrade_link' => 'https://www.example.com?some=var', - 'jetpack-boost_learn_more_link' => 'https://www.example.com', ] ); From 8d537afc832e4e4fca0c633349e870c201dddc8d Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 9 Sep 2024 11:13:17 +0300 Subject: [PATCH 099/313] removes unused variable --- src/integrations/admin/integrations-page.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/integrations/admin/integrations-page.php b/src/integrations/admin/integrations-page.php index 9528bead8da..59102cc063c 100644 --- a/src/integrations/admin/integrations-page.php +++ b/src/integrations/admin/integrations-page.php @@ -123,7 +123,6 @@ public function enqueue_assets() { $algolia_file = 'wp-search-with-algolia/algolia.php'; $old_algolia_file = 'search-by-algolia-instant-relevant-results/algolia.php'; - $host = \YoastSEO()->helpers->url->get_url_host( \get_site_url() ); $addon_manager = new WPSEO_Addon_Manager(); $woocommerce_seo_installed = $addon_manager->is_installed( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ); From efe9aa1d90bab73ebd69c557cf5732e0986ffd58 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 9 Sep 2024 11:15:53 +0300 Subject: [PATCH 100/313] php fix cs --- src/integrations/admin/integrations-page.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/integrations/admin/integrations-page.php b/src/integrations/admin/integrations-page.php index 59102cc063c..4a950f68e46 100644 --- a/src/integrations/admin/integrations-page.php +++ b/src/integrations/admin/integrations-page.php @@ -114,8 +114,8 @@ public function enqueue_assets() { $this->admin_asset_manager->enqueue_script( 'integrations-page' ); - $elementor_conditional = new Elementor_Activated_Conditional(); - $jetpack_conditional = new Jetpack_Conditional(); + $elementor_conditional = new Elementor_Activated_Conditional(); + $jetpack_conditional = new Jetpack_Conditional(); $woocommerce_seo_file = 'wpseo-woocommerce/wpseo-woocommerce.php'; $acf_seo_file = 'acf-content-analysis-for-yoast-seo/yoast-acf-analysis.php'; From a44e248aad9bafc49cb3f836f7283ef1e9e1974a Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 9 Sep 2024 11:18:37 +0300 Subject: [PATCH 101/313] update php cs threshold --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index dd95768d495..05ac91adee3 100644 --- a/composer.json +++ b/composer.json @@ -91,7 +91,7 @@ "Yoast\\WP\\SEO\\Composer\\Actions::check_coding_standards" ], "check-cs-thresholds": [ - "@putenv YOASTCS_THRESHOLD_ERRORS=2482", + "@putenv YOASTCS_THRESHOLD_ERRORS=2481", "@putenv YOASTCS_THRESHOLD_WARNINGS=252", "Yoast\\WP\\SEO\\Composer\\Actions::check_cs_thresholds" ], From daa350e45399b45871bbcf0b16ff147b596300c0 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 9 Sep 2024 11:23:17 +0300 Subject: [PATCH 102/313] remove dead code from tests --- .../Admin/Integrations_Page_Integration_Test.php | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/Unit/Integrations/Admin/Integrations_Page_Integration_Test.php b/tests/Unit/Integrations/Admin/Integrations_Page_Integration_Test.php index 5ebbb5a7531..e51203ed69f 100644 --- a/tests/Unit/Integrations/Admin/Integrations_Page_Integration_Test.php +++ b/tests/Unit/Integrations/Admin/Integrations_Page_Integration_Test.php @@ -7,7 +7,6 @@ use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; -use Yoast\WP\SEO\Helpers\Url_Helper; use Yoast\WP\SEO\Helpers\Woocommerce_Helper; use Yoast\WP\SEO\Integrations\Admin\Integrations_Page; use Yoast\WP\SEO\Tests\Unit\TestCase; @@ -120,17 +119,6 @@ public function test_enqueue_assets() { Monkey\Functions\expect( 'get_site_url' ) ->andReturn( 'https://www.example.com' ); - $url_helper = Mockery::mock( Url_Helper::class ); - $url_helper->expects()->get_url_host( 'https://www.example.com' )->andReturn( 'https://www.example.com' ); - $container = $this->create_container_with( - [ - Url_Helper::class => $url_helper, - ] - ); - - Monkey\Functions\expect( 'YoastSEO' ) - ->andReturn( (object) [ 'helpers' => $this->create_helper_surface( $container ) ] ); - Monkey\Functions\expect( 'is_plugin_active' )->times( 5 )->andReturnTrue(); Monkey\Functions\expect( 'wp_nonce_url' )->times( 3 )->andReturn( 'nonce' ); Monkey\Functions\expect( 'self_admin_url' )->times( 3 )->andReturn( 'https://www.example.com' ); From 5c78ec258e303a7cbef2619347e1485832dc85bc Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 11 Sep 2024 09:57:25 +0300 Subject: [PATCH 103/313] refactor: add news seo check to editors script data --- .../framework/integrations/news-seo.php | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/editors/framework/integrations/news-seo.php diff --git a/src/editors/framework/integrations/news-seo.php b/src/editors/framework/integrations/news-seo.php new file mode 100644 index 00000000000..c013eb86aec --- /dev/null +++ b/src/editors/framework/integrations/news-seo.php @@ -0,0 +1,53 @@ +addon_manager = new WPSEO_Addon_Manager(); + } + + /** + * If the plugin is activated. + * + * @return bool If the plugin is activated. + */ + public function is_enabled(): bool { + return \is_plugin_active( $this->addon_manager->get_plugin_file( WPSEO_Addon_Manager::NEWS_SLUG ) ); + } + + /** + * Return this object represented by a key value array. + * + * @return array Returns the name and if the feature is enabled. + */ + public function to_array(): array { + return [ 'isNewsSEOActive' => $this->is_enabled() ]; + } + + /** + * Returns this object represented by a key value structure that is compliant with the script data array. + * + * @return array Returns the legacy key and if the feature is enabled. + */ + public function to_legacy_array(): array { + return [ 'isNewsSEOActive' => $this->is_enabled() ]; + } +} From 13b3326d9da342f58f0f457ab5d900a66de3cd8a Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 11 Sep 2024 09:58:03 +0300 Subject: [PATCH 104/313] refactor: add new path to is news seo active to store --- packages/js/src/redux/reducers/preferences.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/src/redux/reducers/preferences.js b/packages/js/src/redux/reducers/preferences.js index 207303bbb76..d0fc8b5dd1f 100644 --- a/packages/js/src/redux/reducers/preferences.js +++ b/packages/js/src/redux/reducers/preferences.js @@ -32,7 +32,7 @@ function getDefaultState() { useTwitterData: window.wpseoScriptData.metabox.showSocial.twitter, isWincherIntegrationActive: isWincherIntegrationActive(), isInsightsEnabled: get( window, "wpseoScriptData.metabox.isInsightsEnabled", false ), - isNewsEnabled: ! ! window.wpseoAdminL10n.news_seo_is_active, + isNewsEnabled: get( window, "wpseoScriptData.metabox.isNewsSEOActive", false ), isAiFeatureActive: Boolean( window.wpseoAdminL10n.isAiFeatureActive ), }; } From 36601be8b0274bd9159011994a8904eeb6c64b69 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 11 Sep 2024 09:58:24 +0300 Subject: [PATCH 105/313] remove old news seo active property --- inc/class-wpseo-utils.php | 1 - 1 file changed, 1 deletion(-) diff --git a/inc/class-wpseo-utils.php b/inc/class-wpseo-utils.php index 37a8d5a17db..e9885f54529 100644 --- a/inc/class-wpseo-utils.php +++ b/inc/class-wpseo-utils.php @@ -865,7 +865,6 @@ public static function get_admin_l10n() { 'isBreadcrumbsDisabled' => WPSEO_Options::get( 'breadcrumbs-enable', false ) !== true && ! current_theme_supports( 'yoast-seo-breadcrumbs' ), // phpcs:ignore Generic.ControlStructures.DisallowYodaConditions -- Bug: squizlabs/PHP_CodeSniffer#2962. 'isPrivateBlog' => ( (string) get_option( 'blog_public' ) ) === '0', - 'news_seo_is_active' => ( defined( 'WPSEO_NEWS_FILE' ) ), 'isAiFeatureActive' => (bool) WPSEO_Options::get( 'enable_ai_generator' ), ]; From 6c99aaf4378d022da46e44a68bf58e71316055a7 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 11 Sep 2024 09:12:31 +0200 Subject: [PATCH 106/313] Renames. --- admin/class-config.php | 4 ++-- css/src/new-dashboard.css | 2 +- .../admin/first-time-configuration-integration.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/admin/class-config.php b/admin/class-config.php index 1a476084ddd..ee5e4c733ca 100644 --- a/admin/class-config.php +++ b/admin/class-config.php @@ -6,7 +6,7 @@ */ use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; -use Yoast\WP\SEO\Dashboard\User_Interface\General_Page_Integration; +use Yoast\WP\SEO\Dashboard\User_Interface\New_Dashboard_Page_Integration; use Yoast\WP\SEO\Integrations\Academy_Integration; use Yoast\WP\SEO\Integrations\Settings_Integration; use Yoast\WP\SEO\Integrations\Support_Integration; @@ -105,7 +105,7 @@ public function config_page_scripts() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; - if ( in_array( $page, [ WPSEO_Admin::PAGE_IDENTIFIER, General_Page_Integration::PAGE, 'wpseo_workouts' ], true ) ) { + if ( in_array( $page, [ WPSEO_Admin::PAGE_IDENTIFIER, New_Dashboard_Page_Integration::PAGE, 'wpseo_workouts' ], true ) ) { wp_enqueue_media(); $script_data['userEditUrl'] = add_query_arg( 'user_id', '{user_id}', admin_url( 'user-edit.php' ) ); diff --git a/css/src/new-dashboard.css b/css/src/new-dashboard.css index e56fb835b3d..68e7a5cbf77 100644 --- a/css/src/new-dashboard.css +++ b/css/src/new-dashboard.css @@ -1,4 +1,4 @@ -body.seo_page_wpseo_page_dashboard_new { +body.seo_page_wpseo_page_new_dashboard { @apply yst-bg-slate-100; /* Move WP footer behind our content. */ diff --git a/src/integrations/admin/first-time-configuration-integration.php b/src/integrations/admin/first-time-configuration-integration.php index 43118a16a8b..516993b151c 100644 --- a/src/integrations/admin/first-time-configuration-integration.php +++ b/src/integrations/admin/first-time-configuration-integration.php @@ -137,7 +137,7 @@ public function add_first_time_configuration_tab( $dashboard_tabs ) { */ public function enqueue_assets() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved. - if ( ! isset( $_GET['page'] ) || ( $_GET['page'] !== 'wpseo_dashboard' && $_GET['page'] !== 'wpseo_page_dashboard_new' ) || \is_network_admin() ) { + if ( ! isset( $_GET['page'] ) || ( $_GET['page'] !== 'wpseo_dashboard' && $_GET['page'] !== 'wpseo_page_new_dashboard' ) || \is_network_admin() ) { return; } From e09b1e0184798a43a4e845f4cfea99dae706d96a Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 11 Sep 2024 09:14:24 +0200 Subject: [PATCH 107/313] Use constant. --- .../admin/first-time-configuration-integration.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/integrations/admin/first-time-configuration-integration.php b/src/integrations/admin/first-time-configuration-integration.php index 516993b151c..d39338e78e0 100644 --- a/src/integrations/admin/first-time-configuration-integration.php +++ b/src/integrations/admin/first-time-configuration-integration.php @@ -9,6 +9,7 @@ use WPSEO_Shortlinker; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Context\Meta_Tags_Context; +use Yoast\WP\SEO\Dashboard\User_Interface\New_Dashboard_Page_Integration; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Social_Profiles_Helper; @@ -137,7 +138,7 @@ public function add_first_time_configuration_tab( $dashboard_tabs ) { */ public function enqueue_assets() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved. - if ( ! isset( $_GET['page'] ) || ( $_GET['page'] !== 'wpseo_dashboard' && $_GET['page'] !== 'wpseo_page_new_dashboard' ) || \is_network_admin() ) { + if ( ! isset( $_GET['page'] ) || ( $_GET['page'] !== 'wpseo_dashboard' && $_GET['page'] !== New_Dashboard_Page_Integration::PAGE ) || \is_network_admin() ) { return; } From bb717a602137ed73e8405f819b144e9342cd181b Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Wed, 11 Sep 2024 11:13:44 +0300 Subject: [PATCH 108/313] Add annotation for new prop --- packages/js/src/inline-links/inline.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/js/src/inline-links/inline.js b/packages/js/src/inline-links/inline.js index 017e1a27e80..3da86476ef1 100644 --- a/packages/js/src/inline-links/inline.js +++ b/packages/js/src/inline-links/inline.js @@ -34,6 +34,7 @@ import { link as linkSettings } from "./edit-link"; * @param {Function} props.onChange The rich text change handler. * @param {Function} props.speak The speak function. * @param {Function} props.stopAddingLink The stop adding link handler. + * @param {Object} props.contentRef The ref containing the current content element. * * @returns {WPElement} The inline link UI. */ From 079310e8629054b1b9e55822816e1e8965816aad Mon Sep 17 00:00:00 2001 From: marinakoleva Date: Wed, 11 Sep 2024 11:53:09 +0200 Subject: [PATCH 109/313] removing duplicates from transition words --- .../languages/en/config/transitionWords.js | 2 +- .../languages/en/config/twoPartTransitionWords.js | 3 +-- .../languages/tr/config/transitionWords.js | 5 ++--- .../languages/tr/config/twoPartTransitionWords.js | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/yoastseo/src/languageProcessing/languages/en/config/transitionWords.js b/packages/yoastseo/src/languageProcessing/languages/en/config/transitionWords.js index e5ad4f3b4c2..7d6b1bbf2ef 100644 --- a/packages/yoastseo/src/languageProcessing/languages/en/config/transitionWords.js +++ b/packages/yoastseo/src/languageProcessing/languages/en/config/transitionWords.js @@ -40,7 +40,7 @@ export const multipleWords = [ "above all", "after all", "after that", "all in a "to summarize", "to that end", "to the end that", "to this end", "together with", "under those circumstances", "until now", "up against", "up to the present time", "vis a vis", "what's more", "while it may be true", "while this may be true", "with attention to", "with the result that", "with this in mind", "with this intention", "with this purpose in mind", - "without a doubt", "without delay", "without doubt", "without reservation", "according to", "whether or" ]; + "without a doubt", "without delay", "without doubt", "without reservation", "according to", "no sooner" ]; export const allWords = singleWords.concat( multipleWords ); diff --git a/packages/yoastseo/src/languageProcessing/languages/en/config/twoPartTransitionWords.js b/packages/yoastseo/src/languageProcessing/languages/en/config/twoPartTransitionWords.js index 404d175b06c..99e6235c90f 100644 --- a/packages/yoastseo/src/languageProcessing/languages/en/config/twoPartTransitionWords.js +++ b/packages/yoastseo/src/languageProcessing/languages/en/config/twoPartTransitionWords.js @@ -4,5 +4,4 @@ * Returns an array with two-part transition words to be used by the assessments. * @type {Array} The array filled with two-part transition words. */ -export default [ [ "both", "and" ], [ "if", "then" ], [ "not only", "but also" ], [ "neither", "nor" ], [ "either", "or" ], [ "not", "but" ], - [ "whether", "or" ], [ "no sooner", "than" ] ]; +export default [ [ "both", "and" ], [ "not only", "but also" ], [ "neither", "nor" ], [ "either", "or" ], [ "not", "but" ] ]; diff --git a/packages/yoastseo/src/languageProcessing/languages/tr/config/transitionWords.js b/packages/yoastseo/src/languageProcessing/languages/tr/config/transitionWords.js index 5228b00dc17..8ca9c84edf1 100644 --- a/packages/yoastseo/src/languageProcessing/languages/tr/config/transitionWords.js +++ b/packages/yoastseo/src/languageProcessing/languages/tr/config/transitionWords.js @@ -8,7 +8,7 @@ export const singleWords = [ "fakat", "halbuki", "hatta", "üstelik", "ancak", " "genelde", "dolayısıyla", "gelgelelim", "aslında", "doğrusu", "mamafih", "binaenaleyh", "evvelce", "önceden", "şöylelikle", "örneğin", "mesela", "nitekim", "mademki", "şimdi", "halihazırda", "i̇laveten", "aynen", "nazaran", "nedeniyle", "yüzünden", "umumiyetle", "ekseriye", "amacıyla", "gayesiyle", "velhasıl", "ezcümle", "özetlersek", "etraflıca", "tafsilatlı", "genişçe", "bilfiil", "filhakika", "evvela", "i̇lkin", "en önce", - "birincisi", "i̇kincisi", "üçüncüsü", "sonuncusu", "tıpkı", "topyekun", "hem", "ne", "kah", "ister", "ya", "gerekse", "ha", "sayesinde", "sebebiyle", + "birincisi", "i̇kincisi", "üçüncüsü", "sonuncusu", "tıpkı", "topyekun", "hem", "kah", "ister", "ya", "gerekse", "sayesinde", "sebebiyle", "üzere", "göre", "uyarınca", "halen", "gerçekten", "madem", "yoksa" ]; export const multipleWords = [ "o halde", "bundan böyle", "demek ki", "ne yazık ki", "görüldüğü gibi", "i̇lk olarak", "son olarak", "ne var ki", @@ -35,8 +35,7 @@ export const multipleWords = [ "o halde", "bundan böyle", "demek ki", "ne yazı "sözün özü", "en nihayetinde", "uzun uzadıya", "her iki durumda da", "özü itibariyle", "amacı ile", "olması için", "başka bir ifadeyle", "diğer bir deyişle", "i̇lk önce", "bir yandan", "bir taraftan", "hatırlatmak gerekirse", "bu bağlamda", "gel gelelim", "her şey hesaba katılırsa", "bütüne bakıldığında", "belirtildiği gibi", "bir başka ifadeyle", "lafı toparlamak gerekirse", "bu düşünceyle", "bu maksatla", "bu doğrultuda", - "bu niyetle", "hem de", "ne de", "ya da", "gerekse de", "aksi durumda", "bu durum", "olsun olmasın", "olup olmadığı", "diğer yandan", - "öte yandan", "ne ne", "gerek gerek" ]; + "bu niyetle", "ne de", "ya da", "aksi durumda", "bu durum", "olup olmadığı", "diğer yandan", "öte yandan" ]; export const allWords = singleWords.concat( multipleWords ); diff --git a/packages/yoastseo/src/languageProcessing/languages/tr/config/twoPartTransitionWords.js b/packages/yoastseo/src/languageProcessing/languages/tr/config/twoPartTransitionWords.js index 826accc203e..c43a0719f05 100644 --- a/packages/yoastseo/src/languageProcessing/languages/tr/config/twoPartTransitionWords.js +++ b/packages/yoastseo/src/languageProcessing/languages/tr/config/twoPartTransitionWords.js @@ -4,4 +4,4 @@ * Returns an array with two-part transition words to be used by the assessments. * @returns {Array} The array filled with two-part transition words. */ -export default [ [ "hem", "hem de" ], [ "ne", "ne de" ], [ "ya", "ya da" ], [ "gerek", "gerekse de" ] ]; +export default [ [ "ne", "ne" ], [ "gerek", "gerek" ], [ "olsun", "olmasın" ] ]; From 5a319c7315bc9b8078125a27c2fa7790f917540a Mon Sep 17 00:00:00 2001 From: marinakoleva Date: Wed, 11 Sep 2024 11:54:21 +0200 Subject: [PATCH 110/313] updating unit tests after changes to English and Turkish transition words lists --- .../researches/findTransitionWordsSpec.js | 63 +++++++++++++------ 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/packages/yoastseo/spec/languageProcessing/researches/findTransitionWordsSpec.js b/packages/yoastseo/spec/languageProcessing/researches/findTransitionWordsSpec.js index 02a74743d29..69234009b9f 100644 --- a/packages/yoastseo/spec/languageProcessing/researches/findTransitionWordsSpec.js +++ b/packages/yoastseo/spec/languageProcessing/researches/findTransitionWordsSpec.js @@ -4,6 +4,20 @@ import Paper from "../../../src/values/Paper.js"; import EnglishResearcher from "../../../src/languageProcessing/languages/en/Researcher"; import FrenchResearcher from "../../../src/languageProcessing/languages/fr/Researcher"; import JapaneseResearcher from "../../../src/languageProcessing/languages/ja/Researcher"; +import GermanResearcher from "../../../src/languageProcessing/languages/de/Researcher"; +import DutchResearcher from "../../../src/languageProcessing/languages/nl/Researcher"; +import SpanishResearcher from "../../../src/languageProcessing/languages/es/Researcher"; +import ItalianResearcher from "../../../src/languageProcessing/languages/it/Researcher"; +import PortugueseResearcher from "../../../src/languageProcessing/languages/pt/Researcher"; +import CatalanResearcher from "../../../src/languageProcessing/languages/ca/Researcher"; +import PolishResearcher from "../../../src/languageProcessing/languages/pl/Researcher"; +import HungarianResearcher from "../../../src/languageProcessing/languages/hu/Researcher"; +import SwedishResearcher from "../../../src/languageProcessing/languages/sv/Researcher"; +import IndonesianResearcher from "../../../src/languageProcessing/languages/id/Researcher"; +import TurkishResearcher from "../../../src/languageProcessing/languages/tr/Researcher"; +import RussianResearcher from "../../../src/languageProcessing/languages/ru/Researcher"; +import HebrewResearcher from "../../../src/languageProcessing/languages/he/Researcher"; +import ArabicResearcher from "../../../src/languageProcessing/languages/ar/Researcher"; // eslint-disable-next-line max-statements describe( "a test for finding transition words from a string", function() { @@ -41,7 +55,7 @@ describe( "a test for finding transition words from a string", function() { expect( result.transitionWordSentences ).toBe( 1 ); } ); - it( "returns 1 when a two-part transition word is found in a sentence (English)", function() { + it( "returns 1 when a two-part transition word is found in a sentence (English)", function() { // Transition word: either...or. mockPaper = new Paper( "I will either tell you a story, or read you a novel.", { locale: "en_US" } ); result = transitionWordsResearch( mockPaper, new EnglishResearcher( mockPaper ) ); @@ -49,7 +63,7 @@ describe( "a test for finding transition words from a string", function() { expect( result.transitionWordSentences ).toBe( 1 ); } ); - it( "returns 1 when a two-part transition word is found in a sentence, and no transition word in another sentence. (English)", function() { + it( "returns 1 when a two-part transition word is found in a sentence, and no transition word in another sentence. (English)", function() { // Transition word: either...or. mockPaper = new Paper( "I will either tell you a story, or read you a novel. Okay?", { locale: "en_US" } ); result = transitionWordsResearch( mockPaper, new EnglishResearcher( mockPaper ) ); @@ -57,7 +71,7 @@ describe( "a test for finding transition words from a string", function() { expect( result.transitionWordSentences ).toBe( 1 ); } ); - it( "returns 2 when a two-part transition word is found in a sentence, and a transition word in another sentence. (English)", function() { + it( "returns 2 when a two-part transition word is found in a sentence, and a transition word in another sentence. (English)", function() { // Transition words: either...or, unless. mockPaper = new Paper( "I will either tell you a story, or read you a novel. Unless it is about a boy.", { locale: "en_US" } ); result = transitionWordsResearch( mockPaper, new EnglishResearcher( mockPaper ) ); @@ -66,8 +80,8 @@ describe( "a test for finding transition words from a string", function() { } ); it( "returns 2 when a two-part transition word is found in two sentences. (English)", function() { - // Transition words: either...or, if...then. - mockPaper = new Paper( "I will either tell you a story, or read you a novel. If you want, then I will.", { locale: "en_US" } ); + // Transition words: either...or, both...and. + mockPaper = new Paper( "I will either tell you a story, or read you a novel. She was both furious and disappointed.", { locale: "en_US" } ); result = transitionWordsResearch( mockPaper, new EnglishResearcher( mockPaper ) ); expect( result.totalSentences ).toBe( 2 ); expect( result.transitionWordSentences ).toBe( 2 ); @@ -75,9 +89,9 @@ describe( "a test for finding transition words from a string", function() { it( "returns 2 when a two-part transition word is found in two sentences, " + "and an additional transition word is found in one of them. (English)", function() { - // Transition words: either...or, if ...then, as soon as. + // Transition words: either...or, both...and, as soon as. mockPaper = new Paper( "I will either tell you a story about a boy, or read you a novel. " + - "If you want, then I will start as soon as you're ready.", { locale: "en_US" } ); + "I can read it to both you and her as soon as you're ready.", { locale: "en_US" } ); result = transitionWordsResearch( mockPaper, new EnglishResearcher( mockPaper ) ); expect( result.totalSentences ).toBe( 2 ); expect( result.transitionWordSentences ).toBe( 2 ); @@ -159,7 +173,7 @@ describe( "a test for finding transition words from a string", function() { expect( result.transitionWordSentences ).toBe( 0 ); } ); - /*it( "returns 1 when a transition word is found in a sentence (German)", function() { + it( "returns 1 when a transition word is found in a sentence (German)", function() { // Transition word: zuerst. mockPaper = new Paper( "Zuerst werde ich versuchen zu verstehen, warum er so denkt.", { locale: "de_DE" } ); result = transitionWordsResearch( mockPaper, new GermanResearcher( mockPaper ) ); @@ -190,8 +204,8 @@ describe( "a test for finding transition words from a string", function() { expect( result.totalSentences ).toBe( 1 ); expect( result.transitionWordSentences ).toBe( 0 ); } ); -*/ - /* it( "returns 1 when a transition word is found in a sentence (French)", function() { + + it( "returns 1 when a transition word is found in a sentence (French)", function() { // Transition word: deuxièmement. mockPaper = new Paper( "Deuxièmement, il convient de reconnaître la complexité des tâches à entreprendre.", { locale: "fr_FR" } ); result = transitionWordsResearch( mockPaper, new FrenchResearcher( mockPaper ) ); @@ -205,7 +219,7 @@ describe( "a test for finding transition words from a string", function() { result = transitionWordsResearch( mockPaper, new FrenchResearcher( mockPaper ) ); expect( result.totalSentences ).toBe( 1 ); expect( result.transitionWordSentences ).toBe( 1 ); - } );*/ + } ); it( "returns 1 when a transition word with an apostrophe is found in a sentence (French)", function() { // Transition word: quoi qu’il en soit. @@ -215,7 +229,7 @@ describe( "a test for finding transition words from a string", function() { expect( result.transitionWordSentences ).toBe( 1 ); } ); - /*it( "returns 0 when no transition words are present in a sentence (French)", function() { + it( "returns 0 when no transition words are present in a sentence (French)", function() { mockPaper = new Paper( "Une, deux, trois.", { locale: "fr_FR" } ); result = transitionWordsResearch( mockPaper, new FrenchResearcher( mockPaper ) ); expect( result.totalSentences ).toBe( 1 ); @@ -353,8 +367,8 @@ describe( "a test for finding transition words from a string", function() { expect( result.totalSentences ).toBe( 1 ); expect( result.transitionWordSentences ).toBe( 1 ); } ); -*/ - /*it( "returns 1 when a transition word with a punt volat (·) is found in a sentence (Catalan)", function() { + + it( "returns 1 when a transition word with a punt volat (·) is found in a sentence (Catalan)", function() { // Transition word: per il·lustrar. mockPaper = new Paper( "Roma proposa un concurs de curtmetratges per il·lustrar com ha de ser la ciutat ideal", { locale: "ca_ES" } ); result = transitionWordsResearch( mockPaper, new CatalanResearcher( mockPaper ) ); @@ -442,8 +456,9 @@ describe( "a test for finding transition words from a string", function() { result = transitionWordsResearch( mockPaper, new HungarianResearcher( mockPaper ) ); expect( result.totalSentences ).toBe( 1 ); expect( result.transitionWordSentences ).toBe( 1 ); - } );*/ - /*it( "returns 1 when a three-part transition word is found in a sentence (Hungarian)", function() { + } ); + + it( "returns 1 when a three-part transition word is found in a sentence (Hungarian)", function() { // Transition word: nemcsak, hanem, is mockPaper = new Paper( "Nemcsak a csokoládét szeretem, hanem a süteményt is.", { locale: "hu_HU" } ); result = transitionWordsResearch( mockPaper, new HungarianResearcher( mockPaper ) ); @@ -487,7 +502,7 @@ describe( "a test for finding transition words from a string", function() { expect( result.transitionWordSentences ).toBe( 0 ); } ); - it( "returns 1 when a transition word is found in a sentence (Turkish)", function() { + it( "returns 1 when a single word transition word is found in a sentence (Turkish)", function() { // Transition word: ama. mockPaper = new Paper( "Ama durum bu olmayabilir.", { locale: "tr_TR" } ); result = transitionWordsResearch( mockPaper, new TurkishResearcher( mockPaper ) ); @@ -495,9 +510,17 @@ describe( "a test for finding transition words from a string", function() { expect( result.transitionWordSentences ).toBe( 1 ); } ); + it( "returns 1 when two multiple word transition words are found in a sentence (Turkish)", function() { + // Transition words: bir yandan, diğer yandan. + mockPaper = new Paper( "Bir yandan bunu hissediyor, diğer yandan içimden geçenlerin hepsinin boş birer hayal olduğunu düşünüyordum.", { locale: "tr_TR" } ); + result = transitionWordsResearch( mockPaper, new TurkishResearcher( mockPaper ) ); + expect( result.totalSentences ).toBe( 1 ); + expect( result.transitionWordSentences ).toBe( 1 ); + } ); + it( "returns 1 when a two-part transition word is found in a sentence (Turkish)", function() { - // Transition word: hem, hem de. - mockPaper = new Paper( "Hem şapka hem de ceket dolapta.", { locale: "tr_TR" } ); + // Transition word: olsun, olmasın. + mockPaper = new Paper( "Тaşıt koltuklarına takılmış olsun veya olmasın baş yastıkların onayı.", { locale: "tr_TR" } ); result = transitionWordsResearch( mockPaper, new TurkishResearcher( mockPaper ) ); expect( result.totalSentences ).toBe( 1 ); expect( result.transitionWordSentences ).toBe( 1 ); @@ -571,7 +594,7 @@ describe( "a test for finding transition words from a string", function() { expect( result.totalSentences ).toBe( 1 ); expect( result.transitionWordSentences ).toBe( 1 ); } ); -*/ + it( "returns 1 when a (multiple) transition word is found in a language that uses a custom" + " match transition word helper (Japanese)", function() { // Transition word: ゆえに (tokenized: [ "ゆえ", "に" ]) From 5ce3cd8d1ffc0cade963bdecb2743a4bace289fc Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 11 Sep 2024 14:24:45 +0300 Subject: [PATCH 111/313] add tests --- .../integrations/woocommerce-seo.php | 6 +- .../Integrations/WooCommerce_SEO_Test.php | 95 +++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/Editors/Framework/Integrations/WooCommerce_SEO_Test.php diff --git a/src/editors/framework/integrations/woocommerce-seo.php b/src/editors/framework/integrations/woocommerce-seo.php index 5be8ea5625d..4af3e2f735f 100644 --- a/src/editors/framework/integrations/woocommerce-seo.php +++ b/src/editors/framework/integrations/woocommerce-seo.php @@ -19,9 +19,11 @@ class WooCommerce_SEO implements Integration_Data_Provider_Interface { /** * The constructor. + * + * @param WPSEO_Addon_Manager $addon_manager The addon manager. */ - public function __construct() { - $this->addon_manager = new WPSEO_Addon_Manager(); + public function __construct( WPSEO_Addon_Manager $addon_manager ) { + $this->addon_manager = $addon_manager; } /** diff --git a/tests/Unit/Editors/Framework/Integrations/WooCommerce_SEO_Test.php b/tests/Unit/Editors/Framework/Integrations/WooCommerce_SEO_Test.php new file mode 100644 index 00000000000..54338bcecfd --- /dev/null +++ b/tests/Unit/Editors/Framework/Integrations/WooCommerce_SEO_Test.php @@ -0,0 +1,95 @@ +addon_manager = Mockery::mock( WPSEO_Addon_Manager::class ); + $this->instance = new WooCommerce_SEO( $this->addon_manager ); + } + + /** + * Tests the is_enabled method. + * + * @dataProvider data_provider_is_enabled + * + * @param bool $woocommerce_seo_enabled If the woocommerce plugin is enabled. + * @param bool $expected The expected outcome. + * + * @return void + */ + public function test_is_enabled( + bool $woocommerce_seo_enabled, + bool $expected + ) { + + $this->addon_manager + ->expects( 'get_plugin_file' ) + ->times( 3 ) + ->with( 'yoast-seo-woocommerce' ) + ->andReturn( 'wpseo-woocommerce/wpseo-woocommerce.php' ); + + Monkey\Functions\expect( 'is_plugin_active' ) + ->times( 3 ) + ->with( 'wpseo-woocommerce/wpseo-woocommerce.php' ) + ->andReturn( $woocommerce_seo_enabled ); + + $is_woo_seo_active = $this->instance->is_enabled(); + + $this->assertSame( $expected, $is_woo_seo_active ); + $this->assertSame( [ 'isWooCommerceSeoActive' => $is_woo_seo_active ], $this->instance->to_legacy_array() ); + $this->assertSame( [ 'isWooCommerceSeoActive' => $is_woo_seo_active ], $this->instance->to_array() ); + } + + /** + * Data provider for test_is_enabled. + * + * @return array> + */ + public static function data_provider_is_enabled() { + return [ + 'Enabled' => [ + 'woocommerce_seo_enabled' => true, + 'expected' => true, + ], + 'Disabled' => [ + 'woocommerce_seo_enabled' => false, + 'expected' => false, + ], + ]; + } +} From 9edfb08641862a7dd1500587d2dcded48241746c Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 11 Sep 2024 14:35:49 +0300 Subject: [PATCH 112/313] refactor: changed naming to match woocommerce checks in different PR --- packages/js/src/redux/reducers/preferences.js | 2 +- src/editors/framework/integrations/news-seo.php | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/js/src/redux/reducers/preferences.js b/packages/js/src/redux/reducers/preferences.js index d0fc8b5dd1f..5292a6ff030 100644 --- a/packages/js/src/redux/reducers/preferences.js +++ b/packages/js/src/redux/reducers/preferences.js @@ -32,7 +32,7 @@ function getDefaultState() { useTwitterData: window.wpseoScriptData.metabox.showSocial.twitter, isWincherIntegrationActive: isWincherIntegrationActive(), isInsightsEnabled: get( window, "wpseoScriptData.metabox.isInsightsEnabled", false ), - isNewsEnabled: get( window, "wpseoScriptData.metabox.isNewsSEOActive", false ), + isNewsEnabled: get( window, "wpseoScriptData.metabox.isNewsSeoActive", false ), isAiFeatureActive: Boolean( window.wpseoAdminL10n.isAiFeatureActive ), }; } diff --git a/src/editors/framework/integrations/news-seo.php b/src/editors/framework/integrations/news-seo.php index c013eb86aec..6ad2c5cc77d 100644 --- a/src/editors/framework/integrations/news-seo.php +++ b/src/editors/framework/integrations/news-seo.php @@ -19,9 +19,11 @@ class News_SEO implements Integration_Data_Provider_Interface { /** * The constructor. + * + * @param WPSEO_Addon_Manager $addon_manager The addon manager. */ - public function __construct() { - $this->addon_manager = new WPSEO_Addon_Manager(); + public function __construct( WPSEO_Addon_Manager $addon_manager ) { + $this->addon_manager = $addon_manager; } /** @@ -39,7 +41,7 @@ public function is_enabled(): bool { * @return array Returns the name and if the feature is enabled. */ public function to_array(): array { - return [ 'isNewsSEOActive' => $this->is_enabled() ]; + return [ 'isNewsSeoActive' => $this->is_enabled() ]; } /** @@ -48,6 +50,6 @@ public function to_array(): array { * @return array Returns the legacy key and if the feature is enabled. */ public function to_legacy_array(): array { - return [ 'isNewsSEOActive' => $this->is_enabled() ]; + return [ 'isNewsSeoActive' => $this->is_enabled() ]; } } From b811e227dcaa18e335cd2a35ae41b3e9a5b9703b Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 11 Sep 2024 14:40:46 +0300 Subject: [PATCH 113/313] tests: added unit tests to News_SEO integration class and fix php cs --- .../framework/integrations/news-seo.php | 2 +- .../Framework/Integrations/News_SEO_Test.php | 95 +++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/Editors/Framework/Integrations/News_SEO_Test.php diff --git a/src/editors/framework/integrations/news-seo.php b/src/editors/framework/integrations/news-seo.php index 6ad2c5cc77d..d7059dc8511 100644 --- a/src/editors/framework/integrations/news-seo.php +++ b/src/editors/framework/integrations/news-seo.php @@ -19,7 +19,7 @@ class News_SEO implements Integration_Data_Provider_Interface { /** * The constructor. - * + * * @param WPSEO_Addon_Manager $addon_manager The addon manager. */ public function __construct( WPSEO_Addon_Manager $addon_manager ) { diff --git a/tests/Unit/Editors/Framework/Integrations/News_SEO_Test.php b/tests/Unit/Editors/Framework/Integrations/News_SEO_Test.php new file mode 100644 index 00000000000..00bc0f10a6a --- /dev/null +++ b/tests/Unit/Editors/Framework/Integrations/News_SEO_Test.php @@ -0,0 +1,95 @@ +addon_manager = Mockery::mock( WPSEO_Addon_Manager::class ); + $this->instance = new News_SEO( $this->addon_manager ); + } + + /** + * Tests the is_enabled method. + * + * @dataProvider data_provider_is_enabled + * + * @param bool $news_seo_enabled If the news plugin is enabled. + * @param bool $expected The expected outcome. + * + * @return void + */ + public function test_is_enabled( + bool $news_seo_enabled, + bool $expected + ) { + + $this->addon_manager + ->expects( 'get_plugin_file' ) + ->times( 3 ) + ->with( 'yoast-seo-news' ) + ->andReturn( 'wpseo-news/wpseo-news.php' ); + + Monkey\Functions\expect( 'is_plugin_active' ) + ->times( 3 ) + ->with( 'wpseo-news/wpseo-news.php' ) + ->andReturn( $news_seo_enabled ); + + $is_woo_seo_active = $this->instance->is_enabled(); + + $this->assertSame( $expected, $is_woo_seo_active ); + $this->assertSame( [ 'isNewsSeoActive' => $is_woo_seo_active ], $this->instance->to_legacy_array() ); + $this->assertSame( [ 'isNewsSeoActive' => $is_woo_seo_active ], $this->instance->to_array() ); + } + + /** + * Data provider for test_is_enabled. + * + * @return array> + */ + public static function data_provider_is_enabled() { + return [ + 'Enabled' => [ + 'news_seo_enabled' => true, + 'expected' => true, + ], + 'Disabled' => [ + 'news_seo_enabled' => false, + 'expected' => false, + ], + ]; + } +} From 5134c56408d371c688893254de106af069840ed0 Mon Sep 17 00:00:00 2001 From: marinakoleva Date: Wed, 11 Sep 2024 14:28:02 +0200 Subject: [PATCH 114/313] adding one more addition to the Turkish transition words list --- .../languageProcessing/languages/tr/config/transitionWords.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoastseo/src/languageProcessing/languages/tr/config/transitionWords.js b/packages/yoastseo/src/languageProcessing/languages/tr/config/transitionWords.js index 8ca9c84edf1..73551b2f81c 100644 --- a/packages/yoastseo/src/languageProcessing/languages/tr/config/transitionWords.js +++ b/packages/yoastseo/src/languageProcessing/languages/tr/config/transitionWords.js @@ -35,7 +35,7 @@ export const multipleWords = [ "o halde", "bundan böyle", "demek ki", "ne yazı "sözün özü", "en nihayetinde", "uzun uzadıya", "her iki durumda da", "özü itibariyle", "amacı ile", "olması için", "başka bir ifadeyle", "diğer bir deyişle", "i̇lk önce", "bir yandan", "bir taraftan", "hatırlatmak gerekirse", "bu bağlamda", "gel gelelim", "her şey hesaba katılırsa", "bütüne bakıldığında", "belirtildiği gibi", "bir başka ifadeyle", "lafı toparlamak gerekirse", "bu düşünceyle", "bu maksatla", "bu doğrultuda", - "bu niyetle", "ne de", "ya da", "aksi durumda", "bu durum", "olup olmadığı", "diğer yandan", "öte yandan" ]; + "bu niyetle", "ne de", "ya da", "aksi durumda", "bu durum", "olup olmadığı", "diğer yandan", "öte yandan", "ne olursa olsun" ]; export const allWords = singleWords.concat( multipleWords ); From 068db4e04663582c7e892e3fcc0cef9b22f9d797 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 11 Sep 2024 15:49:35 +0200 Subject: [PATCH 115/313] Extract FTC stept to own component to be reused in new dashboard. --- packages/js/src/dashboard/app.js | 4 +- .../routes/first-time-configuration.js | 26 ++ packages/js/src/dashboard/routes/index.js | 2 + packages/js/src/first-time-configuration.js | 4 +- .../first-time-configuration-app-container.js | 24 ++ .../first-time-configuration-steps.js | 256 +++++++++--------- 6 files changed, 181 insertions(+), 135 deletions(-) create mode 100644 packages/js/src/dashboard/routes/first-time-configuration.js create mode 100644 packages/js/src/dashboard/routes/index.js create mode 100644 packages/js/src/first-time-configuration/first-time-configuration-app-container.js diff --git a/packages/js/src/dashboard/app.js b/packages/js/src/dashboard/app.js index efaf66a6a54..22d9da54941 100644 --- a/packages/js/src/dashboard/app.js +++ b/packages/js/src/dashboard/app.js @@ -7,9 +7,9 @@ import { Paper, SidebarNavigation, Title, useSvgAria } from "@yoast/ui-library"; import classNames from "classnames"; import PropTypes from "prop-types"; import { Link, Route, Routes, useLocation } from "react-router-dom"; -import FirstTimeConfigurationSteps from "../first-time-configuration/first-time-configuration-steps"; import { MenuItemLink, YoastLogo } from "../shared-admin/components"; import { useSelectDashboard } from "./hooks"; +import { FirstTimeConfiguration } from "./routes"; /** * @param {string} [idSuffix] Extra id suffix. Can prevent double IDs on the page. @@ -105,7 +105,7 @@ const App = () => { > } /> - } /> + } /> diff --git a/packages/js/src/dashboard/routes/first-time-configuration.js b/packages/js/src/dashboard/routes/first-time-configuration.js new file mode 100644 index 00000000000..f4f7482f7e1 --- /dev/null +++ b/packages/js/src/dashboard/routes/first-time-configuration.js @@ -0,0 +1,26 @@ +import { __ } from "@wordpress/i18n"; + +import FirstTimeConfigurationSteps from "../../first-time-configuration/first-time-configuration-steps"; +import { + + RouteLayout, +} from "../components"; + +/** + * @returns {JSX.Element} The site defaults route. + */ +const FirstTimeConfiguration = () => { + return ( + +
+
+ +
+
+ ); +}; + +export default FirstTimeConfiguration; diff --git a/packages/js/src/dashboard/routes/index.js b/packages/js/src/dashboard/routes/index.js new file mode 100644 index 00000000000..6efe3db4442 --- /dev/null +++ b/packages/js/src/dashboard/routes/index.js @@ -0,0 +1,2 @@ +export { default as FirstTimeConfiguration } from "./first-time-configuration"; + diff --git a/packages/js/src/first-time-configuration.js b/packages/js/src/first-time-configuration.js index 5534851fd2c..c7a0c8cb216 100644 --- a/packages/js/src/first-time-configuration.js +++ b/packages/js/src/first-time-configuration.js @@ -2,7 +2,7 @@ import domReady from "@wordpress/dom-ready"; import { render } from "@wordpress/element"; import { Root } from "@yoast/ui-library"; import { get } from "lodash"; -import FirstTimeConfigurationSteps from "./first-time-configuration/first-time-configuration-steps"; +import FirstTimeConfigurationAppContainer from "./first-time-configuration/first-time-configuration-app-container"; domReady( () => { const context = { @@ -15,7 +15,7 @@ domReady( () => { render( - + , root ); diff --git a/packages/js/src/first-time-configuration/first-time-configuration-app-container.js b/packages/js/src/first-time-configuration/first-time-configuration-app-container.js new file mode 100644 index 00000000000..72bad2a13c8 --- /dev/null +++ b/packages/js/src/first-time-configuration/first-time-configuration-app-container.js @@ -0,0 +1,24 @@ +import { __ } from "@wordpress/i18n"; +import FirstTimeConfigurationSteps from "./first-time-configuration-steps"; + +/** + * The first time configuration. + * + * @returns {WPElement} The FirstTimeConfigurationSteps component. + */ +export default function FirstTimeConfigurationAppContainer() { + return (
+

{ __( "Tell us about your site, so we can get it ranked!", "wordpress-seo" ) }

+

+ { __( "Let's get your site in tip-top shape for the search engines. Simply follow these 5 steps to make Google understand what your site is about.", "wordpress-seo" ) } +

+
+
+ +
+
); +} + diff --git a/packages/js/src/first-time-configuration/first-time-configuration-steps.js b/packages/js/src/first-time-configuration/first-time-configuration-steps.js index d02d327eb8d..9808f0a43d8 100644 --- a/packages/js/src/first-time-configuration/first-time-configuration-steps.js +++ b/packages/js/src/first-time-configuration/first-time-configuration-steps.js @@ -16,7 +16,7 @@ import FinishStep from "./tailwind-components/steps/finish/finish-step"; import { STEPS } from "./constants"; /* eslint-disable complexity */ - +/* eslint-disable react/jsx-no-bind */ /** * Updates the site representation in the database. * @@ -469,140 +469,134 @@ export default function FirstTimeConfigurationSteps() { }, [ beforeUnloadEventHandler ] ); return ( -
-

{ __( "Tell us about your site, so we can get it ranked!", "wordpress-seo" ) }

-

- { __( "Let's get your site in tip-top shape for the search engines. Simply follow these 5 steps to make Google understand what your site is about.", "wordpress-seo" ) } -

-
- { /* eslint-disable react/jsx-no-bind */ } -
- + + + + { __( "Edit", "wordpress-seo" ) } + + + + + + { __( "Continue", "wordpress-seo" ) } + + + + + + + { __( "Edit", "wordpress-seo" ) } + + + + + + + + + + + + { __( "Edit", "wordpress-seo" ) } + + + + + + + + + + - - - - { __( "Edit", "wordpress-seo" ) } - - - - - - { __( "Continue", "wordpress-seo" ) } - - - - - - - { __( "Edit", "wordpress-seo" ) } - - - - - - - - - - - - { __( "Edit", "wordpress-seo" ) } - - - - - - - - - - - - { __( "Edit", "wordpress-seo" ) } - - - - - - - - - - - - - - - -
-
+ + { __( "Edit", "wordpress-seo" ) } + + + + + + + + + + + + + + + ); } /* eslint-enable max-len */ /* eslint-enable complexity */ +/* eslint-enable react/jsx-no-bind */ /* eslint-enable max-statements */ From e0ce4e9a5d7011e5802c71841b38ebcf9e27b241 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 12 Sep 2024 10:04:52 +0300 Subject: [PATCH 116/313] restore woocommerce selectors restore selectors --- packages/js/src/components/SchemaTab.js | 4 ++-- packages/js/src/components/fills/MetaboxFill.js | 2 +- packages/js/src/redux/selectors/preferences.js | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/js/src/components/SchemaTab.js b/packages/js/src/components/SchemaTab.js index 2db8c4abb12..4d2c2c5cf24 100644 --- a/packages/js/src/components/SchemaTab.js +++ b/packages/js/src/components/SchemaTab.js @@ -69,7 +69,7 @@ NewsAlert.propTypes = { */ const getSchemaTypeOptions = ( schemaTypeOptions, defaultType, postTypeName ) => { const isProduct = useSelect( ( select ) => select( STORE ).getIsProduct(), [] ); - const isWooSeoActive = useSelect( select => select( STORE ).getPreference( "isWooCommerceSeoActive" ), [] ); + const isWooSeoActive = useSelect( select => select( STORE ).getIsWooSeoActive(), [] ); const disablePageTypeSelect = isProduct && isWooSeoActive; const schemaOption = disablePageTypeSelect ? { name: __( "Item Page", "wordpress-seo" ), value: "ItemPage" } : schemaTypeOptions.find( option => option.value === defaultType ); return [ @@ -171,7 +171,7 @@ const Content = ( props ) => { const [ focusedArticleType, setFocusedArticleType ] = useState( props.schemaArticleTypeSelected ); const woocommerceUpsellText = __( "Want your products stand out in search results with rich results like price, reviews and more?", "wordpress-seo" ); const isProduct = useSelect( ( select ) => select( STORE ).getIsProduct(), [] ); - const isWooSeoActive = useSelect( select => select( STORE ).getPreference( "isWooCommerceSeoActive" ), [] ); + const isWooSeoActive = useSelect( select => select( STORE ).getIsWooSeoActive(), [] ); const settingsLink = useSelect( select => select( STORE ).selectAdminLink( "?page=wpseo_page_settings" ), [] ); const disablePageTypeSelect = isProduct && isWooSeoActive; diff --git a/packages/js/src/components/fills/MetaboxFill.js b/packages/js/src/components/fills/MetaboxFill.js index 31e110d77ab..95197220fae 100644 --- a/packages/js/src/components/fills/MetaboxFill.js +++ b/packages/js/src/components/fills/MetaboxFill.js @@ -39,7 +39,7 @@ const BlackFridayPromotionWithMetaboxWarningsCheck = withMetaboxWarningsCheck( B export default function MetaboxFill( { settings } ) { const isTerm = useSelect( ( select ) => select( "yoast-seo/editor" ).getIsTerm(), [] ); const isProduct = useSelect( ( select ) => select( "yoast-seo/editor" ).getIsProduct(), [] ); - const isWooCommerceActive = useSelect( ( select ) => select( "yoast-seo/editor" ).getPreference( "isWooCommerceActive" ), [] ); + const isWooCommerceActive = useSelect( ( select ) => select( "yoast-seo/editor" ).getIsWooCommerceActive(), [] ); const shouldShowWooCommerceChecklistPromo = isProduct && isWooCommerceActive; diff --git a/packages/js/src/redux/selectors/preferences.js b/packages/js/src/redux/selectors/preferences.js index 54b83b7f675..8d4004ac660 100644 --- a/packages/js/src/redux/selectors/preferences.js +++ b/packages/js/src/redux/selectors/preferences.js @@ -43,3 +43,19 @@ export const getIsWooSeoUpsell = ( state ) => { return ! isWooSeoActive && isWooCommerceActive && isProductPage; }; + +/** + * Get the preference for the isWooCommerceActive. + * + * @param {Object} state The state. + * @returns {Boolean} The preference for the isWooCommerceActive. + */ +export const getIsWooCommerceActive = state => getPreference( state, "isWooCommerceActive", false ); + +/** + * Get the preference for the isWooSeoActive. + * + * @param {Object} state The state. + * @returns {Boolean} The preference for the isWooSeoActive. + */ +export const getIsWooSeoActive = state => getPreference( state, "isWooSeoActive", false ); From 0fb8050d95334d15370ea9769b03965fe20481aa Mon Sep 17 00:00:00 2001 From: Mykola Shlyakhtun Date: Thu, 12 Sep 2024 10:23:04 +0300 Subject: [PATCH 117/313] "Keyphrase in slug" assessment won't ever make sense on a static homepage #20118 --- .../seo/UrlKeywordAssessmentSpec.js | 28 +++++++++++++++++++ .../assessments/seo/UrlKeywordAssessment.js | 7 +++-- src/integrations/settings-integration.php | 1 + 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/yoastseo/spec/scoring/assessments/seo/UrlKeywordAssessmentSpec.js b/packages/yoastseo/spec/scoring/assessments/seo/UrlKeywordAssessmentSpec.js index 5503e2fa84a..dab87f57121 100644 --- a/packages/yoastseo/spec/scoring/assessments/seo/UrlKeywordAssessmentSpec.js +++ b/packages/yoastseo/spec/scoring/assessments/seo/UrlKeywordAssessmentSpec.js @@ -93,6 +93,10 @@ describe( "A keyword in slug count assessment", function() { } ); describe( "tests for the assessment applicability.", function() { + afterEach( () => { + window.wpseoScriptData = undefined; + } ); + it( "returns false when there is no keyword and slug found.", function() { const paper = new Paper( "sample keyword" ); const researcher = new DefaultResearcher( paper ); @@ -122,12 +126,36 @@ describe( "tests for the assessment applicability.", function() { expect( keywordCountInSlug.isApplicable( paper, researcher ) ).toBe( false ); } ); + it( "returns false when page is front.", function() { + const paper = new Paper( "sample keyword", { + slug: "sample-with-keyword", + keyword: "keyword", + } ); + + window.wpseoScriptData = { + metabox: { + isFrontPage: true, + }, + }; + + // The default researcher has the keywordCountInSlug research. + const researcher = new DefaultResearcher( paper ); + + expect( keywordCountInSlug.isApplicable( paper, researcher ) ).toBe( false ); + } ); + it( "returns true when the researcher has the keywordCountInSlug research.", function() { const paper = new Paper( "sample keyword", { slug: "sample-with-keyword", keyword: "keyword", } ); + window.wpseoScriptData = { + metabox: { + isFrontPage: false, + }, + }; + // The default researcher has the keywordCountInSlug research. const researcher = new DefaultResearcher( paper ); diff --git a/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js b/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js index 890a076e622..3a5df1c979f 100644 --- a/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js +++ b/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js @@ -1,5 +1,5 @@ import { __, sprintf } from "@wordpress/i18n"; -import { merge } from "lodash"; +import { merge, get } from "lodash"; import Assessment from "../assessment"; import { createAnchorOpeningTag } from "../../../helpers/shortlinker"; @@ -63,7 +63,10 @@ class SlugKeywordAssessment extends Assessment { * @returns {boolean} True if the paper contains a keyword and a slug, and if the keywordCountInSlug research is available on the researcher. */ isApplicable( paper, researcher ) { - return paper.hasKeyword() && paper.hasSlug() && researcher.hasResearch( "keywordCountInSlug" ); + console.log('isApplicable ', window.wpseoScriptData); + const isFrontPage = get( window, "wpseoScriptData.metabox.isFrontPage", false ); + console.log("isFrontPage", isFrontPage); + return !isFrontPage && paper.hasKeyword() && paper.hasSlug() && researcher.hasResearch( "keywordCountInSlug" ); } /** diff --git a/src/integrations/settings-integration.php b/src/integrations/settings-integration.php index 6b3dce07333..6a148297c73 100644 --- a/src/integrations/settings-integration.php +++ b/src/integrations/settings-integration.php @@ -516,6 +516,7 @@ protected function get_preferences( $settings ) { 'upsellSettings' => $this->get_upsell_settings(), 'siteRepresentsPerson' => $this->get_site_represents_person( $settings ), 'siteBasicsPolicies' => $this->get_site_basics_policies( $settings ), + 'isFrontPage' => \is_front_page(), ]; } From 554cbe6704dbf327b741a55aa3245f0c8a26dc45 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 12 Sep 2024 11:09:50 +0300 Subject: [PATCH 118/313] cleanup: remove outdated userLanguage property --- admin/class-config.php | 1 - admin/metabox/class-metabox.php | 1 - admin/taxonomy/class-taxonomy.php | 1 - src/integrations/third-party/elementor.php | 2 -- 4 files changed, 5 deletions(-) diff --git a/admin/class-config.php b/admin/class-config.php index ee5e4c733ca..f8367968ae7 100644 --- a/admin/class-config.php +++ b/admin/class-config.php @@ -92,7 +92,6 @@ public function config_page_scripts() { $dismissed_alerts = $alert_dismissal_action->all_dismissed(); $script_data = [ - 'userLanguageCode' => WPSEO_Language_Utils::get_language( get_user_locale() ), 'dismissedAlerts' => $dismissed_alerts, 'isRtl' => is_rtl(), 'isPremium' => YoastSEO()->helpers->product->is_premium(), diff --git a/admin/metabox/class-metabox.php b/admin/metabox/class-metabox.php index c875ea3d1fa..c4e4c208b39 100644 --- a/admin/metabox/class-metabox.php +++ b/admin/metabox/class-metabox.php @@ -884,7 +884,6 @@ public function enqueue() { $script_data = [ 'metabox' => $this->get_metabox_script_data(), - 'userLanguageCode' => WPSEO_Language_Utils::get_language( get_user_locale() ), 'isPost' => true, 'isBlockEditor' => $is_block_editor, 'postId' => $post_id, diff --git a/admin/taxonomy/class-taxonomy.php b/admin/taxonomy/class-taxonomy.php index 2ace8a9b849..62b0bd2e766 100644 --- a/admin/taxonomy/class-taxonomy.php +++ b/admin/taxonomy/class-taxonomy.php @@ -182,7 +182,6 @@ public function admin_enqueue_scripts() { ], ], 'metabox' => $this->localize_term_scraper_script( $tag_id ), - 'userLanguageCode' => WPSEO_Language_Utils::get_language( get_user_locale() ), 'isTerm' => true, 'postId' => $tag_id, 'termType' => $this->get_taxonomy(), diff --git a/src/integrations/third-party/elementor.php b/src/integrations/third-party/elementor.php index 1f82056c2b5..a57dceeeee3 100644 --- a/src/integrations/third-party/elementor.php +++ b/src/integrations/third-party/elementor.php @@ -6,7 +6,6 @@ use WP_Screen; use WPSEO_Admin_Asset_Manager; use WPSEO_Admin_Recommended_Replace_Vars; -use WPSEO_Language_Utils; use WPSEO_Meta; use WPSEO_Metabox_Analysis_Inclusive_Language; use WPSEO_Metabox_Analysis_Readability; @@ -411,7 +410,6 @@ public function enqueue() { $script_data = [ 'metabox' => $this->get_metabox_script_data( $permalink ), - 'userLanguageCode' => WPSEO_Language_Utils::get_language( \get_user_locale() ), 'isPost' => true, 'isBlockEditor' => WP_Screen::get()->is_block_editor(), 'isElementorEditor' => true, From 4f595a2d4171bcfb0eb2a1749ed485a4ee6de024 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 12 Sep 2024 10:48:03 +0200 Subject: [PATCH 119/313] Add additional check. --- src/builders/indexable-link-builder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/builders/indexable-link-builder.php b/src/builders/indexable-link-builder.php index 4fbe978c15e..476600a8899 100644 --- a/src/builders/indexable-link-builder.php +++ b/src/builders/indexable-link-builder.php @@ -599,10 +599,10 @@ public function update_first_content_image( Indexable $indexable, array $images $first_content_image_url = \key( $images ); $first_content_image_id = \current( $images ); - if ( $indexable->open_graph_image_source === 'first-content-image' && $current_open_graph_image === $first_content_image_url ) { + if ( $indexable->open_graph_image_source === 'first-content-image' && $current_open_graph_image === $first_content_image_url && ! empty( $first_content_image_id ) ) { $indexable->open_graph_image_id = $first_content_image_id; } - if ( $indexable->twitter_image_source === 'first-content-image' && $current_twitter_image === $first_content_image_url ) { + if ( $indexable->twitter_image_source === 'first-content-image' && $current_twitter_image === $first_content_image_url && ! empty( $first_content_image_id ) ) { $indexable->twitter_image_id = $first_content_image_id; } } From 82d9ae2a5830792c3a2f499198da140718461cd1 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 12 Sep 2024 11:49:59 +0300 Subject: [PATCH 120/313] fix: preference for is woo seo active --- packages/js/src/redux/selectors/preferences.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/src/redux/selectors/preferences.js b/packages/js/src/redux/selectors/preferences.js index 8d4004ac660..037c2912b7a 100644 --- a/packages/js/src/redux/selectors/preferences.js +++ b/packages/js/src/redux/selectors/preferences.js @@ -58,4 +58,4 @@ export const getIsWooCommerceActive = state => getPreference( state, "isWooComme * @param {Object} state The state. * @returns {Boolean} The preference for the isWooSeoActive. */ -export const getIsWooSeoActive = state => getPreference( state, "isWooSeoActive", false ); +export const getIsWooSeoActive = state => getPreference( state, "isWooCommerceSeoActive", false ); From 77787993abeb33e9c3f58178140f25f982720e9e Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 12 Sep 2024 10:54:08 +0200 Subject: [PATCH 121/313] Threshold. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 05ac91adee3..5e0d93dc3db 100644 --- a/composer.json +++ b/composer.json @@ -91,7 +91,7 @@ "Yoast\\WP\\SEO\\Composer\\Actions::check_coding_standards" ], "check-cs-thresholds": [ - "@putenv YOASTCS_THRESHOLD_ERRORS=2481", + "@putenv YOASTCS_THRESHOLD_ERRORS=2480", "@putenv YOASTCS_THRESHOLD_WARNINGS=252", "Yoast\\WP\\SEO\\Composer\\Actions::check_cs_thresholds" ], From ebbd0274fe298c477ab875b6847910a0f5687079 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 12 Sep 2024 11:58:55 +0300 Subject: [PATCH 122/313] fix: getIsWooUpsell selector --- packages/js/src/redux/selectors/preferences.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js/src/redux/selectors/preferences.js b/packages/js/src/redux/selectors/preferences.js index 037c2912b7a..55735a5f620 100644 --- a/packages/js/src/redux/selectors/preferences.js +++ b/packages/js/src/redux/selectors/preferences.js @@ -37,8 +37,8 @@ export const getIsKeywordAnalysisActive = state => get( state, "preferences.isKe * @returns {Boolean} Whether the plugin is WooCommerce SEO or not. */ export const getIsWooSeoUpsell = ( state ) => { - const isWooSeoActive = getPreference( state, "isWooSeoActive" ); - const isWooCommerceActive = getPreference( state, "isWooCommerceActive" ); + const isWooSeoActive = getIsWooSeoActive( state ); + const isWooCommerceActive = getIsWooCommerceActive( state ); const isProductPage = getIsProduct( state ); return ! isWooSeoActive && isWooCommerceActive && isProductPage; From 2525460fb2af27a85720e53c7ec67d50cc5e87d1 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 12 Sep 2024 12:01:23 +0300 Subject: [PATCH 123/313] refactor: change order of execution for woocommerce selectors --- .../js/src/redux/selectors/preferences.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/js/src/redux/selectors/preferences.js b/packages/js/src/redux/selectors/preferences.js index 55735a5f620..5e3feb4282f 100644 --- a/packages/js/src/redux/selectors/preferences.js +++ b/packages/js/src/redux/selectors/preferences.js @@ -30,20 +30,6 @@ export const getPreferences = state => state.preferences; */ export const getIsKeywordAnalysisActive = state => get( state, "preferences.isKeywordAnalysisActive", false ); -/** - * Determines whether the WooCommerce SEO addon is not active in a product page. - * - * @param {Object} state The state. - * @returns {Boolean} Whether the plugin is WooCommerce SEO or not. - */ -export const getIsWooSeoUpsell = ( state ) => { - const isWooSeoActive = getIsWooSeoActive( state ); - const isWooCommerceActive = getIsWooCommerceActive( state ); - const isProductPage = getIsProduct( state ); - - return ! isWooSeoActive && isWooCommerceActive && isProductPage; -}; - /** * Get the preference for the isWooCommerceActive. * @@ -59,3 +45,17 @@ export const getIsWooCommerceActive = state => getPreference( state, "isWooComme * @returns {Boolean} The preference for the isWooSeoActive. */ export const getIsWooSeoActive = state => getPreference( state, "isWooCommerceSeoActive", false ); + +/** + * Determines whether the WooCommerce SEO addon is not active in a product page. + * + * @param {Object} state The state. + * @returns {Boolean} Whether the plugin is WooCommerce SEO or not. + */ +export const getIsWooSeoUpsell = ( state ) => { + const isWooSeoActive = getIsWooSeoActive( state ); + const isWooCommerceActive = getIsWooCommerceActive( state ); + const isProductPage = getIsProduct( state ); + + return ! isWooSeoActive && isWooCommerceActive && isProductPage; +}; From d857cc9b04f0ee604f5ee21910afcf603c2af005 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 12 Sep 2024 13:52:52 +0300 Subject: [PATCH 124/313] refactor: remove isPrivateBlog to the editors directory --- inc/class-wpseo-utils.php | 2 -- packages/js/src/redux/reducers/preferences.js | 2 +- src/editors/framework/site/base-site-information.php | 2 ++ 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/inc/class-wpseo-utils.php b/inc/class-wpseo-utils.php index e9885f54529..dc73f126664 100644 --- a/inc/class-wpseo-utils.php +++ b/inc/class-wpseo-utils.php @@ -863,8 +863,6 @@ public static function get_admin_l10n() { 'postTypeNamePlural' => ( $page_type === 'post' ) ? $label_object->label : $label_object->name, 'postTypeNameSingular' => ( $page_type === 'post' ) ? $label_object->labels->singular_name : $label_object->singular_name, 'isBreadcrumbsDisabled' => WPSEO_Options::get( 'breadcrumbs-enable', false ) !== true && ! current_theme_supports( 'yoast-seo-breadcrumbs' ), - // phpcs:ignore Generic.ControlStructures.DisallowYodaConditions -- Bug: squizlabs/PHP_CodeSniffer#2962. - 'isPrivateBlog' => ( (string) get_option( 'blog_public' ) ) === '0', 'isAiFeatureActive' => (bool) WPSEO_Options::get( 'enable_ai_generator' ), ]; diff --git a/packages/js/src/redux/reducers/preferences.js b/packages/js/src/redux/reducers/preferences.js index 36d7ad8e482..2308200865c 100644 --- a/packages/js/src/redux/reducers/preferences.js +++ b/packages/js/src/redux/reducers/preferences.js @@ -22,7 +22,7 @@ function getDefaultState() { isWordFormRecognitionActive: isUndefined( window.wpseoPremiumMetaboxData ) && isWordFormRecognitionActive(), isCornerstoneActive: isCornerstoneActive(), isBreadcrumbsDisabled: ! ! window.wpseoAdminL10n.isBreadcrumbsDisabled, - isPrivateBlog: ! ! window.wpseoAdminL10n.isPrivateBlog, + isPrivateBlog: ! ! window.wpseoScriptData.isPrivateBlog, isSEMrushIntegrationActive: isSEMrushIntegrationActive(), shouldUpsell: isUndefined( window.wpseoPremiumMetaboxData ), displayAdvancedTab: displayAdvancedTab, diff --git a/src/editors/framework/site/base-site-information.php b/src/editors/framework/site/base-site-information.php index f62b8181eba..3d284139149 100644 --- a/src/editors/framework/site/base-site-information.php +++ b/src/editors/framework/site/base-site-information.php @@ -80,6 +80,7 @@ public function get_site_information(): array { 'isRtl' => \is_rtl(), 'isPremium' => $this->product_helper->is_premium(), 'siteIconUrl' => \get_site_icon_url(), + 'isPrivateBlog' => ! \get_option( 'blog_public' ), ]; } @@ -95,6 +96,7 @@ public function get_legacy_site_information(): array { 'linkParams' => $this->short_link_helper->get_query_params(), 'pluginUrl' => \plugins_url( '', \WPSEO_FILE ), 'wistiaEmbedPermission' => $this->wistia_embed_permission_repository->get_value_for_user( \get_current_user_id() ), + 'isPrivateBlog' => ! \get_option( 'blog_public' ), 'metabox' => [ 'site_name' => $this->meta->for_current_page()->site_name, 'contentLocale' => \get_locale(), From 924b706915534d974392b28d6c795d7e20e16834 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 12 Sep 2024 13:53:15 +0300 Subject: [PATCH 125/313] test: updated tests for isPrivateBlog --- .../Framework/Site/Post_Site_Information_Test.php | 8 ++++++-- .../Framework/Site/Term_Site_Information_Test.php | 7 ++++++- .../Framework/Site/Post_Site_Information_Test.php | 12 ++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php index c3ecc881b8f..bd6d1ca9627 100644 --- a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php @@ -136,7 +136,7 @@ public function test_legacy_site_information() { ], 'pluginUrl' => '/location', 'wistiaEmbedPermission' => true, - + 'isPrivateBlog' => true, ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); @@ -191,11 +191,15 @@ public function test_site_information() { 'isRtl' => false, 'isPremium' => true, 'siteIconUrl' => 'https://example.org', - + 'isPrivateBlog' => false, ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); Monkey\Functions\expect( 'home_url' )->andReturn( 'https://example.org' ); + Monkey\Functions\expect( 'get_option' ) + ->once() + ->with( 'blog_public' ) + ->andReturn( '1' ); $this->alert_dismissal_action->expects( 'all_dismissed' )->andReturn( [ 'the alert' ] ); $this->promotion_manager->expects( 'get_current_promotions' )->andReturn( [ 'the promotion', 'another one' ] ); diff --git a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php index b659aecb752..cbc2cc777bc 100644 --- a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php @@ -127,10 +127,15 @@ public function test_site_information() { 'isRtl' => false, 'isPremium' => true, 'siteIconUrl' => 'https://example.org', + 'isPrivateBlog' => true, ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); Monkey\Functions\expect( 'home_url' )->andReturn( 'https://example.org' ); + Monkey\Functions\expect( 'get_option' ) + ->once() + ->with( 'blog_public' ) + ->andReturn( '0' ); $this->assertSame( $expected, $this->instance->get_site_information() ); } @@ -168,7 +173,7 @@ public function test_legacy_site_information() { ], 'pluginUrl' => '/location', 'wistiaEmbedPermission' => true, - + 'isPrivateBlog' => true, ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); diff --git a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php index a7034a1c5c5..6ac0446ed4f 100644 --- a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php @@ -124,6 +124,7 @@ public function test_legacy_site_information() { 'linkParams' => $this->short_link_helper->get_query_params(), 'pluginUrl' => 'http://example.org/wp-content/plugins/wordpress-seo', 'wistiaEmbedPermission' => true, + 'isPrivateBlog' => false, ]; $this->assertSame( $expected, $this->instance->get_legacy_site_information() ); @@ -142,6 +143,8 @@ public function test_legacy_site_information() { * @return void */ public function test_site_information() { + \update_option( 'blog_public', '0' ); + $expected = [ 'dismissedAlerts' => false, 'currentPromotions' => [], @@ -160,9 +163,14 @@ public function test_site_information() { 'isRtl' => false, 'isPremium' => false, 'siteIconUrl' => '', - + 'isPrivateBlog' => true, ]; - $this->assertSame( $expected, $this->instance->get_site_information() ); + $site_info = $this->instance->get_site_information(); + + // Reset the blog_public option before the next test. + \update_option( 'blog_public', '1' ); + + $this->assertSame( $expected, $site_info ); } } From 32e06827d97cd25bb9a3902ac789407ec6adfc59 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 12 Sep 2024 16:00:00 +0300 Subject: [PATCH 126/313] refactor: sitewide_social_image * Removes from metabox formatter * Added and renaming to base-site-information.php * Update names --- admin/formatter/class-metabox-formatter.php | 1 - packages/js/src/elementor/initializers/editor-store.js | 2 +- packages/js/src/initializers/editor-store.js | 2 +- src/editors/framework/site/base-site-information.php | 3 +++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/admin/formatter/class-metabox-formatter.php b/admin/formatter/class-metabox-formatter.php index 687361c79ff..e3006f3b1fd 100644 --- a/admin/formatter/class-metabox-formatter.php +++ b/admin/formatter/class-metabox-formatter.php @@ -52,7 +52,6 @@ private function get_defaults() { $defaults = [ 'author_name' => get_the_author_meta( 'display_name' ), - 'sitewide_social_image' => WPSEO_Options::get( 'og_default_image' ), 'keyword_usage' => [], 'title_template' => '', 'metadesc_template' => '', diff --git a/packages/js/src/elementor/initializers/editor-store.js b/packages/js/src/elementor/initializers/editor-store.js index 03e3991a0ad..c4f6d108e83 100644 --- a/packages/js/src/elementor/initializers/editor-store.js +++ b/packages/js/src/elementor/initializers/editor-store.js @@ -25,7 +25,7 @@ const populateStore = store => { store.dispatch( actions.setSettings( { socialPreviews: { - sitewideImage: window.wpseoScriptData.metabox.sitewide_social_image, + sitewideImage: window.wpseoScriptData.sitewideSocialImage, siteName: window.wpseoScriptData.metabox.site_name, contentImage: window.wpseoScriptData.metabox.first_content_image, twitterCardType: window.wpseoScriptData.metabox.twitterCardType, diff --git a/packages/js/src/initializers/editor-store.js b/packages/js/src/initializers/editor-store.js index 3d015eda9d0..26d17325135 100644 --- a/packages/js/src/initializers/editor-store.js +++ b/packages/js/src/initializers/editor-store.js @@ -14,7 +14,7 @@ const populateStore = store => { store.dispatch( actions.setSettings( { socialPreviews: { - sitewideImage: window.wpseoScriptData.metabox.sitewide_social_image, + sitewideImage: window.wpseoScriptData.sitewideSocialImage, siteName: window.wpseoScriptData.metabox.site_name, contentImage: window.wpseoScriptData.metabox.first_content_image, twitterCardType: window.wpseoScriptData.metabox.twitterCardType, diff --git a/src/editors/framework/site/base-site-information.php b/src/editors/framework/site/base-site-information.php index f62b8181eba..ebb2066b466 100644 --- a/src/editors/framework/site/base-site-information.php +++ b/src/editors/framework/site/base-site-information.php @@ -3,6 +3,7 @@ namespace Yoast\WP\SEO\Editors\Framework\Site; use Exception; +use WPSEO_Options; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; @@ -80,6 +81,7 @@ public function get_site_information(): array { 'isRtl' => \is_rtl(), 'isPremium' => $this->product_helper->is_premium(), 'siteIconUrl' => \get_site_icon_url(), + 'sitewideSocialImage' => WPSEO_Options::get( 'og_default_image' ), ]; } @@ -95,6 +97,7 @@ public function get_legacy_site_information(): array { 'linkParams' => $this->short_link_helper->get_query_params(), 'pluginUrl' => \plugins_url( '', \WPSEO_FILE ), 'wistiaEmbedPermission' => $this->wistia_embed_permission_repository->get_value_for_user( \get_current_user_id() ), + 'sitewideSocialImage' => WPSEO_Options::get( 'og_default_image' ), 'metabox' => [ 'site_name' => $this->meta->for_current_page()->site_name, 'contentLocale' => \get_locale(), From 3a339df4f7e5b663dbe9f0e0aa6e9ff9612fbedc Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 12 Sep 2024 16:05:57 +0300 Subject: [PATCH 127/313] tests: dded tests for sitewide social image --- .../Editors/Framework/Site/Post_Site_Information_Test.php | 4 ++-- .../Editors/Framework/Site/Term_Site_Information_Test.php | 3 ++- .../WP/Editors/Framework/Site/Post_Site_Information_Test.php | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php index c3ecc881b8f..3d011d3be47 100644 --- a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php @@ -136,7 +136,7 @@ public function test_legacy_site_information() { ], 'pluginUrl' => '/location', 'wistiaEmbedPermission' => true, - + 'sitewideSocialImage' => null, ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); @@ -191,7 +191,7 @@ public function test_site_information() { 'isRtl' => false, 'isPremium' => true, 'siteIconUrl' => 'https://example.org', - + 'sitewideSocialImage' => null, ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); diff --git a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php index b659aecb752..3f3057c87e6 100644 --- a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php @@ -127,6 +127,7 @@ public function test_site_information() { 'isRtl' => false, 'isPremium' => true, 'siteIconUrl' => 'https://example.org', + 'sitewideSocialImage' => null, ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); @@ -168,7 +169,7 @@ public function test_legacy_site_information() { ], 'pluginUrl' => '/location', 'wistiaEmbedPermission' => true, - + 'sitewideSocialImage' => null, ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); diff --git a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php index a7034a1c5c5..445bdfb9ce3 100644 --- a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php @@ -124,6 +124,7 @@ public function test_legacy_site_information() { 'linkParams' => $this->short_link_helper->get_query_params(), 'pluginUrl' => 'http://example.org/wp-content/plugins/wordpress-seo', 'wistiaEmbedPermission' => true, + 'sitewideSocialImage' => '', ]; $this->assertSame( $expected, $this->instance->get_legacy_site_information() ); @@ -160,7 +161,7 @@ public function test_site_information() { 'isRtl' => false, 'isPremium' => false, 'siteIconUrl' => '', - + 'sitewideSocialImage' => '', ]; $this->assertSame( $expected, $this->instance->get_site_information() ); From 6565d5ef7e1621698a82fd374053251c2f3b5668 Mon Sep 17 00:00:00 2001 From: Igor <35524806+igorschoester@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:29:16 +0200 Subject: [PATCH 128/313] Fix SelectControl deprecation warning Gutenberg added a deprecation warning for SelectControl via https://github.com/WordPress/gutenberg/issues/38730 This opts in to that behavior and adds the margin top to the ExternalLink below (therefor needing block layout to get the margin to take effect) --- packages/js/src/components/PrimaryTaxonomyPicker.js | 2 +- packages/js/src/components/TaxonomyPicker.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/js/src/components/PrimaryTaxonomyPicker.js b/packages/js/src/components/PrimaryTaxonomyPicker.js index cbf46617f95..ab5be3801d4 100644 --- a/packages/js/src/components/PrimaryTaxonomyPicker.js +++ b/packages/js/src/components/PrimaryTaxonomyPicker.js @@ -244,7 +244,7 @@ class PrimaryTaxonomyPicker extends Component { id={ fieldId } terms={ this.state.selectedTerms } /> - + { __( "Learn more", "wordpress-seo" ) } { __( "Learn more about the primary category.", "wordpress-seo" ) } diff --git a/packages/js/src/components/TaxonomyPicker.js b/packages/js/src/components/TaxonomyPicker.js index 2dd803fd966..1b0f4f8afdd 100644 --- a/packages/js/src/components/TaxonomyPicker.js +++ b/packages/js/src/components/TaxonomyPicker.js @@ -21,6 +21,7 @@ const TaxonomyPicker = ( { id, value, terms, label, onChange } ) => { return ( Date: Thu, 12 Sep 2024 19:04:30 +0300 Subject: [PATCH 129/313] refactor: move logic to premium --- packages/js/src/components/contentAnalysis/SeoAnalysis.js | 3 +-- packages/js/src/redux/reducers/preferences.js | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/js/src/components/contentAnalysis/SeoAnalysis.js b/packages/js/src/components/contentAnalysis/SeoAnalysis.js index 184283991ba..5cd89ea6f3c 100644 --- a/packages/js/src/components/contentAnalysis/SeoAnalysis.js +++ b/packages/js/src/components/contentAnalysis/SeoAnalysis.js @@ -324,7 +324,6 @@ export default withSelect( ( select, ownProps ) => { getMarksButtonStatus, getResultsForKeyword, getIsElementorEditor, - getPreference, } = select( "yoast-seo/editor" ); const keyword = getFocusKeyphrase(); @@ -334,6 +333,6 @@ export default withSelect( ( select, ownProps ) => { marksButtonStatus: ownProps.hideMarksButtons ? "disabled" : getMarksButtonStatus(), keyword, isElementor: getIsElementorEditor(), - isAiFeatureEnabled: getPreference( "isAiFeatureActive", false ), + isAiFeatureEnabled: select( "yoast-seo-premium/editor" )?.getIsAiFeatureEnabled(), }; } )( SeoAnalysis ); diff --git a/packages/js/src/redux/reducers/preferences.js b/packages/js/src/redux/reducers/preferences.js index 36d7ad8e482..e8f45e5c0bb 100644 --- a/packages/js/src/redux/reducers/preferences.js +++ b/packages/js/src/redux/reducers/preferences.js @@ -33,7 +33,6 @@ function getDefaultState() { isWincherIntegrationActive: isWincherIntegrationActive(), isInsightsEnabled: get( window, "wpseoScriptData.metabox.isInsightsEnabled", false ), isNewsEnabled: get( window, "wpseoScriptData.metabox.isNewsSeoActive", false ), - isAiFeatureActive: Boolean( window.wpseoAdminL10n.isAiFeatureActive ), isWooCommerceSeoActive: get( window, "wpseoScriptData.metabox.isWooCommerceSeoActive", false ), isWooCommerceActive: get( window, "wpseoScriptData.metabox.isWooCommerceActive", false ), }; From f0f8267c2ebd109fc49c04e9036d9c4026ec69ee Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 13 Sep 2024 09:53:08 +0300 Subject: [PATCH 130/313] refactor: remove unused property --- inc/class-wpseo-utils.php | 1 - 1 file changed, 1 deletion(-) diff --git a/inc/class-wpseo-utils.php b/inc/class-wpseo-utils.php index e9885f54529..5c8ac6534c3 100644 --- a/inc/class-wpseo-utils.php +++ b/inc/class-wpseo-utils.php @@ -865,7 +865,6 @@ public static function get_admin_l10n() { 'isBreadcrumbsDisabled' => WPSEO_Options::get( 'breadcrumbs-enable', false ) !== true && ! current_theme_supports( 'yoast-seo-breadcrumbs' ), // phpcs:ignore Generic.ControlStructures.DisallowYodaConditions -- Bug: squizlabs/PHP_CodeSniffer#2962. 'isPrivateBlog' => ( (string) get_option( 'blog_public' ) ) === '0', - 'isAiFeatureActive' => (bool) WPSEO_Options::get( 'enable_ai_generator' ), ]; $additional_entries = apply_filters( 'wpseo_admin_l10n', [] ); From 4c7d6ba15a7d8ccc39e5009c8ba9a09e33f0b0e6 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 13 Sep 2024 10:10:39 +0300 Subject: [PATCH 131/313] refactor: premium check is not needed We are using premium store for this property. --- packages/js/src/components/contentAnalysis/SeoAnalysis.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/src/components/contentAnalysis/SeoAnalysis.js b/packages/js/src/components/contentAnalysis/SeoAnalysis.js index 5cd89ea6f3c..bd9f7a16fcd 100644 --- a/packages/js/src/components/contentAnalysis/SeoAnalysis.js +++ b/packages/js/src/components/contentAnalysis/SeoAnalysis.js @@ -209,7 +209,7 @@ class SeoAnalysis extends Component { const isPremium = getL10nObject().isPremium; // Don't show the button if the AI feature is not enabled for Yoast SEO Premium users. - if ( isPremium && ! this.props.isAiFeatureEnabled ) { + if ( ! this.props.isAiFeatureEnabled ) { return; } From 1d31f3d022913045f84caf8b195ab48b86a68cb4 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 13 Sep 2024 14:35:16 +0300 Subject: [PATCH 132/313] refactor: move showSocial property to base-site-information --- admin/formatter/class-metabox-formatter.php | 4 ---- src/editors/framework/site/base-site-information.php | 9 +++++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/admin/formatter/class-metabox-formatter.php b/admin/formatter/class-metabox-formatter.php index 687361c79ff..13a4c507b76 100644 --- a/admin/formatter/class-metabox-formatter.php +++ b/admin/formatter/class-metabox-formatter.php @@ -56,10 +56,6 @@ private function get_defaults() { 'keyword_usage' => [], 'title_template' => '', 'metadesc_template' => '', - 'showSocial' => [ - 'facebook' => WPSEO_Options::get( 'opengraph', false ), - 'twitter' => WPSEO_Options::get( 'twitter', false ), - ], 'schema' => [ 'displayFooter' => WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ), 'pageTypeOptions' => $schema_types->get_page_type_options(), diff --git a/src/editors/framework/site/base-site-information.php b/src/editors/framework/site/base-site-information.php index f62b8181eba..d51f44543f1 100644 --- a/src/editors/framework/site/base-site-information.php +++ b/src/editors/framework/site/base-site-information.php @@ -3,6 +3,7 @@ namespace Yoast\WP\SEO\Editors\Framework\Site; use Exception; +use WPSEO_Options; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; @@ -80,6 +81,10 @@ public function get_site_information(): array { 'isRtl' => \is_rtl(), 'isPremium' => $this->product_helper->is_premium(), 'siteIconUrl' => \get_site_icon_url(), + 'showSocial' => [ + 'facebook' => WPSEO_Options::get( 'opengraph', false ), + 'twitter' => WPSEO_Options::get( 'twitter', false ), + ], ]; } @@ -102,6 +107,10 @@ public function get_legacy_site_information(): array { 'isRtl' => \is_rtl(), 'isPremium' => $this->product_helper->is_premium(), 'siteIconUrl' => \get_site_icon_url(), + 'showSocial' => [ + 'facebook' => WPSEO_Options::get( 'opengraph', false ), + 'twitter' => WPSEO_Options::get( 'twitter', false ), + ], ], ]; } From 25908b29104917132a717b16dad687dd88f0e06e Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 13 Sep 2024 14:35:52 +0300 Subject: [PATCH 133/313] tests: adjusts tests for showSocial property --- .../Framework/Site/Post_Site_Information_Test.php | 9 ++++++++- .../Framework/Site/Term_Site_Information_Test.php | 8 ++++++++ .../Framework/Site/Post_Site_Information_Test.php | 8 ++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php index c3ecc881b8f..010a9d0b5fc 100644 --- a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php @@ -127,6 +127,10 @@ public function test_legacy_site_information() { 'isRtl' => false, 'isPremium' => true, 'siteIconUrl' => 'https://example.org', + 'showSocial' => [ + 'facebook' => false, + 'twitter' => false, + ], ], 'adminUrl' => 'https://example.org', @@ -191,7 +195,10 @@ public function test_site_information() { 'isRtl' => false, 'isPremium' => true, 'siteIconUrl' => 'https://example.org', - + 'showSocial' => [ + 'facebook' => false, + 'twitter' => false, + ], ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); diff --git a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php index b659aecb752..2475fb2836b 100644 --- a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php @@ -127,6 +127,10 @@ public function test_site_information() { 'isRtl' => false, 'isPremium' => true, 'siteIconUrl' => 'https://example.org', + 'showSocial' => [ + 'facebook' => false, + 'twitter' => false, + ], ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); @@ -160,6 +164,10 @@ public function test_legacy_site_information() { 'isRtl' => false, 'isPremium' => true, 'siteIconUrl' => 'https://example.org', + 'showSocial' => [ + 'facebook' => false, + 'twitter' => false, + ], ], 'adminUrl' => 'https://example.org', 'linkParams' => [ diff --git a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php index a7034a1c5c5..f72691ce3d5 100644 --- a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php @@ -119,6 +119,10 @@ public function test_legacy_site_information() { 'isRtl' => false, 'isPremium' => false, 'siteIconUrl' => '', + 'showSocial' => [ + 'facebook' => true, + 'twitter' => true, + ], ], 'adminUrl' => 'http://example.org/wp-admin/admin.php', 'linkParams' => $this->short_link_helper->get_query_params(), @@ -160,6 +164,10 @@ public function test_site_information() { 'isRtl' => false, 'isPremium' => false, 'siteIconUrl' => '', + 'showSocial' => [ + 'facebook' => true, + 'twitter' => true, + ], ]; From c40765dc65091260a781ec24a159a6434fadce3a Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 13 Sep 2024 17:11:07 +0300 Subject: [PATCH 134/313] refactor: restore check for private blog --- src/editors/framework/site/base-site-information.php | 6 ++++-- .../Editors/Framework/Site/Post_Site_Information_Test.php | 2 +- .../Editors/Framework/Site/Term_Site_Information_Test.php | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/editors/framework/site/base-site-information.php b/src/editors/framework/site/base-site-information.php index 3d284139149..a69fe1bc889 100644 --- a/src/editors/framework/site/base-site-information.php +++ b/src/editors/framework/site/base-site-information.php @@ -80,7 +80,8 @@ public function get_site_information(): array { 'isRtl' => \is_rtl(), 'isPremium' => $this->product_helper->is_premium(), 'siteIconUrl' => \get_site_icon_url(), - 'isPrivateBlog' => ! \get_option( 'blog_public' ), + // phpcs:ignore Generic.ControlStructures.DisallowYodaConditions -- Bug: squizlabs/PHP_CodeSniffer#2962. + 'isPrivateBlog' => ( (string) \get_option( 'blog_public' ) ) === '0', ]; } @@ -96,7 +97,8 @@ public function get_legacy_site_information(): array { 'linkParams' => $this->short_link_helper->get_query_params(), 'pluginUrl' => \plugins_url( '', \WPSEO_FILE ), 'wistiaEmbedPermission' => $this->wistia_embed_permission_repository->get_value_for_user( \get_current_user_id() ), - 'isPrivateBlog' => ! \get_option( 'blog_public' ), + // phpcs:ignore Generic.ControlStructures.DisallowYodaConditions -- Bug: squizlabs/PHP_CodeSniffer#2962. + 'isPrivateBlog' => ( (string) \get_option( 'blog_public' ) ) === '0', 'metabox' => [ 'site_name' => $this->meta->for_current_page()->site_name, 'contentLocale' => \get_locale(), diff --git a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php index bd6d1ca9627..e545b539f6a 100644 --- a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php @@ -136,7 +136,7 @@ public function test_legacy_site_information() { ], 'pluginUrl' => '/location', 'wistiaEmbedPermission' => true, - 'isPrivateBlog' => true, + 'isPrivateBlog' => false, ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); diff --git a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php index cbc2cc777bc..44723b8eed2 100644 --- a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php @@ -173,7 +173,7 @@ public function test_legacy_site_information() { ], 'pluginUrl' => '/location', 'wistiaEmbedPermission' => true, - 'isPrivateBlog' => true, + 'isPrivateBlog' => false, ]; Monkey\Functions\expect( 'admin_url' )->andReturn( 'https://example.org' ); From 057028ebb2313244bcbef78088c065b9b17a3298 Mon Sep 17 00:00:00 2001 From: Mykola Shlyakhtun Date: Mon, 16 Sep 2024 10:23:32 +0300 Subject: [PATCH 135/313] "Keyphrase in slug" assessment won't ever make sense on a static homepage #20118 Propagate isFrontPage from settings. --- admin/metabox/class-metabox.php | 1 + packages/js/src/analysis/collectAnalysisData.js | 2 ++ .../src/scoring/assessments/seo/UrlKeywordAssessment.js | 5 +---- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/admin/metabox/class-metabox.php b/admin/metabox/class-metabox.php index c875ea3d1fa..acbc5629472 100644 --- a/admin/metabox/class-metabox.php +++ b/admin/metabox/class-metabox.php @@ -896,6 +896,7 @@ public function enqueue() { 'worker' => $worker_script_data, ], 'isWooCommerceSeoActive' => $woocommerce_seo_active, + 'isFrontPage' => get_option( 'page_on_front' ) === $post_id, ]; /** diff --git a/packages/js/src/analysis/collectAnalysisData.js b/packages/js/src/analysis/collectAnalysisData.js index 0cd64657b08..59b8ea1bdd5 100644 --- a/packages/js/src/analysis/collectAnalysisData.js +++ b/packages/js/src/analysis/collectAnalysisData.js @@ -2,6 +2,7 @@ import { applyFilters } from "@wordpress/hooks"; import { cloneDeep, merge, + get, } from "lodash"; import { serialize } from "@wordpress/blocks"; @@ -96,6 +97,7 @@ export default function collectAnalysisData( editorData, store, customAnalysisDa data.shortcodes = window.wpseoScriptData.analysis.plugins.shortcodes ? window.wpseoScriptData.analysis.plugins.shortcodes.wpseo_shortcode_tags : []; + data.isFrontPage = get( window, "wpseoScriptData.isFrontPage", "0" ) === "1"; return Paper.parse( applyFilters( "yoast.analysis.data", data ) ); } diff --git a/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js b/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js index 3a5df1c979f..ed512d8b016 100644 --- a/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js +++ b/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js @@ -63,10 +63,7 @@ class SlugKeywordAssessment extends Assessment { * @returns {boolean} True if the paper contains a keyword and a slug, and if the keywordCountInSlug research is available on the researcher. */ isApplicable( paper, researcher ) { - console.log('isApplicable ', window.wpseoScriptData); - const isFrontPage = get( window, "wpseoScriptData.metabox.isFrontPage", false ); - console.log("isFrontPage", isFrontPage); - return !isFrontPage && paper.hasKeyword() && paper.hasSlug() && researcher.hasResearch( "keywordCountInSlug" ); + return !paper.isFrontPage() && paper.hasKeyword() && paper.hasSlug() && researcher.hasResearch( "keywordCountInSlug" ); } /** From 40392c3276c11a51b28d63c848fb1983f7447ae1 Mon Sep 17 00:00:00 2001 From: Mykola Shlyakhtun Date: Mon, 16 Sep 2024 10:23:55 +0300 Subject: [PATCH 136/313] "Keyphrase in slug" assessment won't ever make sense on a static homepage #20118 Paper settings. --- packages/yoastseo/src/values/Paper.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/yoastseo/src/values/Paper.js b/packages/yoastseo/src/values/Paper.js index 3a72398258e..97b6466bcfb 100644 --- a/packages/yoastseo/src/values/Paper.js +++ b/packages/yoastseo/src/values/Paper.js @@ -19,6 +19,7 @@ const defaultAttributes = { textTitle: "", writingDirection: "LTR", wpBlocks: [], + isFrontPage: false, }; /** @@ -202,6 +203,14 @@ Paper.prototype.getSlug = function() { return this._attributes.slug; }; +/** + * Checks if currently editing page is a front page + * @returns {boolean} Returns true current page is a front page. + */ +Paper.prototype.isFrontPage = function() { + return this._attributes.isFrontPage; +}; + /** * Checks whether an url is available * @deprecated Since version 18.7. Use hasSlug instead. From 24d5534f8fdb4805614d4cffb2678778ec3b9b1a Mon Sep 17 00:00:00 2001 From: Mykola Shlyakhtun Date: Mon, 16 Sep 2024 10:32:10 +0300 Subject: [PATCH 137/313] "Keyphrase in slug" assessment won't ever make sense on a static homepage #20118 Fix linting. --- .../src/scoring/assessments/seo/UrlKeywordAssessment.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js b/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js index ed512d8b016..b3a7c9418e3 100644 --- a/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js +++ b/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js @@ -1,5 +1,5 @@ import { __, sprintf } from "@wordpress/i18n"; -import { merge, get } from "lodash"; +import { merge } from "lodash"; import Assessment from "../assessment"; import { createAnchorOpeningTag } from "../../../helpers/shortlinker"; @@ -63,7 +63,7 @@ class SlugKeywordAssessment extends Assessment { * @returns {boolean} True if the paper contains a keyword and a slug, and if the keywordCountInSlug research is available on the researcher. */ isApplicable( paper, researcher ) { - return !paper.isFrontPage() && paper.hasKeyword() && paper.hasSlug() && researcher.hasResearch( "keywordCountInSlug" ); + return ! paper.isFrontPage() && paper.hasKeyword() && paper.hasSlug() && researcher.hasResearch( "keywordCountInSlug" ); } /** From 355288f0cd14850df44cc5640d9be6a34cc40417 Mon Sep 17 00:00:00 2001 From: Mykola Shlyakhtun Date: Mon, 16 Sep 2024 10:44:10 +0300 Subject: [PATCH 138/313] "Keyphrase in slug" assessment won't ever make sense on a static homepage #20118 Fix linting. --- .../spec/scoring/assessments/seo/UrlKeywordAssessmentSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoastseo/spec/scoring/assessments/seo/UrlKeywordAssessmentSpec.js b/packages/yoastseo/spec/scoring/assessments/seo/UrlKeywordAssessmentSpec.js index dab87f57121..31eea4aed36 100644 --- a/packages/yoastseo/spec/scoring/assessments/seo/UrlKeywordAssessmentSpec.js +++ b/packages/yoastseo/spec/scoring/assessments/seo/UrlKeywordAssessmentSpec.js @@ -94,7 +94,7 @@ describe( "A keyword in slug count assessment", function() { describe( "tests for the assessment applicability.", function() { afterEach( () => { - window.wpseoScriptData = undefined; + window.wpseoScriptData = { }; } ); it( "returns false when there is no keyword and slug found.", function() { From 66c590e18d06ec370f1d26a05cfd4df152c8fa18 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 16 Sep 2024 13:51:06 +0300 Subject: [PATCH 139/313] refactor: add options helper to site info classes --- .../framework/site/base-site-information.php | 14 ++++++++++++-- .../framework/site/post-site-information.php | 14 ++++++++++++-- .../framework/site/term-site-information.php | 4 ++-- .../Framework/Site/Post_Site_Information_Test.php | 11 ++++++++++- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/editors/framework/site/base-site-information.php b/src/editors/framework/site/base-site-information.php index 30084258492..7d0f695f7f3 100644 --- a/src/editors/framework/site/base-site-information.php +++ b/src/editors/framework/site/base-site-information.php @@ -3,7 +3,7 @@ namespace Yoast\WP\SEO\Editors\Framework\Site; use Exception; -use WPSEO_Options; +use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; @@ -42,6 +42,13 @@ abstract class Base_Site_Information { */ protected $product_helper; + /** + * The options helper. + * + * @var Options_Helper $options_helper + */ + protected $optios_helper; + /** * The constructor. * @@ -50,17 +57,20 @@ abstract class Base_Site_Information { * repository. * @param Meta_Surface $meta The meta surface. * @param Product_Helper $product_helper The product helper. + * @param Options_Helper $options_helper The options helper. */ public function __construct( Short_Link_Helper $short_link_helper, Wistia_Embed_Permission_Repository $wistia_embed_permission_repository, Meta_Surface $meta, - Product_Helper $product_helper + Product_Helper $product_helper, + Options_Helper $options_helper ) { $this->short_link_helper = $short_link_helper; $this->wistia_embed_permission_repository = $wistia_embed_permission_repository; $this->meta = $meta; $this->product_helper = $product_helper; + $this->options_helper = $options_helper; } /** diff --git a/src/editors/framework/site/post-site-information.php b/src/editors/framework/site/post-site-information.php index 4882642aa7a..c6354dcc425 100644 --- a/src/editors/framework/site/post-site-information.php +++ b/src/editors/framework/site/post-site-information.php @@ -3,6 +3,7 @@ namespace Yoast\WP\SEO\Editors\Framework\Site; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; +use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; @@ -35,6 +36,13 @@ class Post_Site_Information extends Base_Site_Information { */ private $promotion_manager; + /** + * The options helper. + * + * @var Options_Helper $options_helper + */ + protected $options_helper; + /** * Constructs the class. * @@ -45,6 +53,7 @@ class Post_Site_Information extends Base_Site_Information { * @param Meta_Surface $meta The meta surface. * @param Product_Helper $product_helper The product helper. * @param Alert_Dismissal_Action $alert_dismissal_action The alert dismissal action. + * @param Options_Helper $options_helper The options helper. * * @return void */ @@ -54,9 +63,10 @@ public function __construct( Wistia_Embed_Permission_Repository $wistia_embed_permission_repository, Meta_Surface $meta, Product_Helper $product_helper, - Alert_Dismissal_Action $alert_dismissal_action + Alert_Dismissal_Action $alert_dismissal_action, + Options_Helper $options_helper ) { - parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper ); + parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper, $options_helper ); $this->promotion_manager = $promotion_manager; $this->alert_dismissal_action = $alert_dismissal_action; } diff --git a/src/editors/framework/site/term-site-information.php b/src/editors/framework/site/term-site-information.php index 90c212f2a5a..f84989e92f7 100644 --- a/src/editors/framework/site/term-site-information.php +++ b/src/editors/framework/site/term-site-information.php @@ -20,7 +20,7 @@ class Term_Site_Information extends Base_Site_Information { * * @var Options_Helper */ - private $options_helper; + protected $options_helper; /** * The taxonomy. @@ -53,7 +53,7 @@ public function __construct( Meta_Surface $meta, Product_Helper $product_helper ) { - parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper ); + parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper, $options_helper ); $this->options_helper = $options_helper; } diff --git a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php index 0f00f76499b..404727b0a81 100644 --- a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php @@ -7,6 +7,7 @@ use Mockery; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; use Yoast\WP\SEO\Editors\Framework\Site\Post_Site_Information; +use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; @@ -68,6 +69,13 @@ final class Post_Site_Information_Test extends TestCase { */ private $product_helper; + /** + * The options helper. + * + * @var Mockery\MockInterface|Options_Helper $options_helper + */ + private $options_helper; + /** * The Post_Site_Information container. * @@ -88,8 +96,9 @@ protected function set_up() { $this->meta_surface = Mockery::mock( Meta_Surface::class ); $this->product_helper = Mockery::mock( Product_Helper::class ); $this->alert_dismissal_action = Mockery::mock( Alert_Dismissal_Action::class ); + $this->options_helper = Mockery::mock( Options_Helper::class ); - $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action ); + $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action, $this->options_helper ); $this->instance->set_permalink( 'perma' ); $this->set_mocks(); } From 0a7eb2d129a89fb91f9070f95116dc592df6c713 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 16 Sep 2024 13:56:21 +0300 Subject: [PATCH 140/313] refactor: implement options helper fix tests --- src/editors/framework/site/base-site-information.php | 4 ++-- .../Framework/Site/Post_Site_Information_Test.php | 2 ++ .../Framework/Site/Term_Site_Information_Test.php | 1 + .../Framework/Site/Post_Site_Information_Test.php | 11 ++++++++++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/editors/framework/site/base-site-information.php b/src/editors/framework/site/base-site-information.php index 7d0f695f7f3..ecb202a6132 100644 --- a/src/editors/framework/site/base-site-information.php +++ b/src/editors/framework/site/base-site-information.php @@ -91,7 +91,7 @@ public function get_site_information(): array { 'isRtl' => \is_rtl(), 'isPremium' => $this->product_helper->is_premium(), 'siteIconUrl' => \get_site_icon_url(), - 'sitewideSocialImage' => WPSEO_Options::get( 'og_default_image' ), + 'sitewideSocialImage' => $this->options_helper->get( 'og_default_image' ), // phpcs:ignore Generic.ControlStructures.DisallowYodaConditions -- Bug: squizlabs/PHP_CodeSniffer#2962. 'isPrivateBlog' => ( (string) \get_option( 'blog_public' ) ) === '0', ]; @@ -109,7 +109,7 @@ public function get_legacy_site_information(): array { 'linkParams' => $this->short_link_helper->get_query_params(), 'pluginUrl' => \plugins_url( '', \WPSEO_FILE ), 'wistiaEmbedPermission' => $this->wistia_embed_permission_repository->get_value_for_user( \get_current_user_id() ), - 'sitewideSocialImage' => WPSEO_Options::get( 'og_default_image' ), + 'sitewideSocialImage' => $this->options_helper->get( 'og_default_image' ), // phpcs:ignore Generic.ControlStructures.DisallowYodaConditions -- Bug: squizlabs/PHP_CodeSniffer#2962. 'isPrivateBlog' => ( (string) \get_option( 'blog_public' ) ) === '0', 'metabox' => [ diff --git a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php index 404727b0a81..c4e820a345a 100644 --- a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php @@ -157,6 +157,7 @@ public function test_legacy_site_information() { $this->promotion_manager->expects( 'get_current_promotions' )->andReturn( [ 'the promotion', 'another one' ] ); $this->promotion_manager->expects( 'is' )->andReturnFalse(); $this->short_link_helper->expects( 'get' )->andReturn( 'https://expl.c' ); + $this->options_helper->expects( 'get' )->with( 'og_default_image' )->andReturn( null ); $this->assertSame( $expected, $this->instance->get_legacy_site_information() ); } @@ -217,6 +218,7 @@ public function test_site_information() { $this->promotion_manager->expects( 'get_current_promotions' )->andReturn( [ 'the promotion', 'another one' ] ); $this->promotion_manager->expects( 'is' )->andReturnFalse(); $this->short_link_helper->expects( 'get' )->andReturn( 'https://expl.c' ); + $this->options_helper->expects( 'get' )->with( 'og_default_image' )->andReturn( null ); $this->assertSame( $expected, $this->instance->get_site_information() ); } diff --git a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php index 7250bdce65c..f104ea4c43a 100644 --- a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php @@ -93,6 +93,7 @@ protected function set_up() { $this->instance->set_term( $mock_term ); $this->options_helper->expects( 'get' )->with( 'stripcategorybase', false )->andReturnFalse(); + $this->options_helper->expects( 'get' )->with( 'og_default_image' )->andReturn( null ); $this->set_mocks(); } diff --git a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php index 327d611695f..194e8796a15 100644 --- a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php @@ -6,6 +6,7 @@ use Mockery; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; use Yoast\WP\SEO\Editors\Framework\Site\Post_Site_Information; +use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; @@ -64,6 +65,13 @@ final class Post_Site_Information_Test extends TestCase { */ private $product_helper; + /** + * The options helper. + * + * @var Mockery\MockInterface|Options_Helper $options_helper + */ + private $options_helper; + /** * The Post_Site_Information container. * @@ -84,9 +92,10 @@ public function set_up() { $this->wistia_embed_repo->expects( 'get_value_for_user' )->with( 0 )->andReturnTrue(); $this->meta_surface = \YoastSEO()->meta; $this->product_helper = \YoastSEO()->helpers->product; + $this->options_helper = \YoastSEO()->helpers->options; $this->alert_dismissal_action = \YoastSEO()->classes->get( Alert_Dismissal_Action::class ); - $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action ); + $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action, $this->options_helper ); $this->instance->set_permalink( 'perma' ); } From 4ddb8827446be7972b0d0ed7a84e4b9d51f84bb5 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 16 Sep 2024 13:51:06 +0300 Subject: [PATCH 141/313] refactor: add options helper to site info classes --- .../framework/site/base-site-information.php | 14 ++++++++++++-- .../framework/site/post-site-information.php | 14 ++++++++++++-- .../framework/site/term-site-information.php | 4 ++-- .../Framework/Site/Post_Site_Information_Test.php | 11 ++++++++++- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/editors/framework/site/base-site-information.php b/src/editors/framework/site/base-site-information.php index 131a038a14d..f472e35bf15 100644 --- a/src/editors/framework/site/base-site-information.php +++ b/src/editors/framework/site/base-site-information.php @@ -3,7 +3,7 @@ namespace Yoast\WP\SEO\Editors\Framework\Site; use Exception; -use WPSEO_Options; +use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; @@ -42,6 +42,13 @@ abstract class Base_Site_Information { */ protected $product_helper; + /** + * The options helper. + * + * @var Options_Helper $options_helper + */ + protected $optios_helper; + /** * The constructor. * @@ -50,17 +57,20 @@ abstract class Base_Site_Information { * repository. * @param Meta_Surface $meta The meta surface. * @param Product_Helper $product_helper The product helper. + * @param Options_Helper $options_helper The options helper. */ public function __construct( Short_Link_Helper $short_link_helper, Wistia_Embed_Permission_Repository $wistia_embed_permission_repository, Meta_Surface $meta, - Product_Helper $product_helper + Product_Helper $product_helper, + Options_Helper $options_helper ) { $this->short_link_helper = $short_link_helper; $this->wistia_embed_permission_repository = $wistia_embed_permission_repository; $this->meta = $meta; $this->product_helper = $product_helper; + $this->options_helper = $options_helper; } /** diff --git a/src/editors/framework/site/post-site-information.php b/src/editors/framework/site/post-site-information.php index 4882642aa7a..c6354dcc425 100644 --- a/src/editors/framework/site/post-site-information.php +++ b/src/editors/framework/site/post-site-information.php @@ -3,6 +3,7 @@ namespace Yoast\WP\SEO\Editors\Framework\Site; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; +use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; @@ -35,6 +36,13 @@ class Post_Site_Information extends Base_Site_Information { */ private $promotion_manager; + /** + * The options helper. + * + * @var Options_Helper $options_helper + */ + protected $options_helper; + /** * Constructs the class. * @@ -45,6 +53,7 @@ class Post_Site_Information extends Base_Site_Information { * @param Meta_Surface $meta The meta surface. * @param Product_Helper $product_helper The product helper. * @param Alert_Dismissal_Action $alert_dismissal_action The alert dismissal action. + * @param Options_Helper $options_helper The options helper. * * @return void */ @@ -54,9 +63,10 @@ public function __construct( Wistia_Embed_Permission_Repository $wistia_embed_permission_repository, Meta_Surface $meta, Product_Helper $product_helper, - Alert_Dismissal_Action $alert_dismissal_action + Alert_Dismissal_Action $alert_dismissal_action, + Options_Helper $options_helper ) { - parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper ); + parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper, $options_helper ); $this->promotion_manager = $promotion_manager; $this->alert_dismissal_action = $alert_dismissal_action; } diff --git a/src/editors/framework/site/term-site-information.php b/src/editors/framework/site/term-site-information.php index 90c212f2a5a..f84989e92f7 100644 --- a/src/editors/framework/site/term-site-information.php +++ b/src/editors/framework/site/term-site-information.php @@ -20,7 +20,7 @@ class Term_Site_Information extends Base_Site_Information { * * @var Options_Helper */ - private $options_helper; + protected $options_helper; /** * The taxonomy. @@ -53,7 +53,7 @@ public function __construct( Meta_Surface $meta, Product_Helper $product_helper ) { - parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper ); + parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper, $options_helper ); $this->options_helper = $options_helper; } diff --git a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php index b74e06a3c19..0a466311339 100644 --- a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php @@ -7,6 +7,7 @@ use Mockery; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; use Yoast\WP\SEO\Editors\Framework\Site\Post_Site_Information; +use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; @@ -68,6 +69,13 @@ final class Post_Site_Information_Test extends TestCase { */ private $product_helper; + /** + * The options helper. + * + * @var Mockery\MockInterface|Options_Helper $options_helper + */ + private $options_helper; + /** * The Post_Site_Information container. * @@ -88,8 +96,9 @@ protected function set_up() { $this->meta_surface = Mockery::mock( Meta_Surface::class ); $this->product_helper = Mockery::mock( Product_Helper::class ); $this->alert_dismissal_action = Mockery::mock( Alert_Dismissal_Action::class ); + $this->options_helper = Mockery::mock( Options_Helper::class ); - $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action ); + $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action, $this->options_helper ); $this->instance->set_permalink( 'perma' ); $this->set_mocks(); } From 4ec1893866ccd3f1ef124712415411c7a4f7633c Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 16 Sep 2024 14:24:11 +0300 Subject: [PATCH 142/313] refactor: implement options helper --- src/editors/framework/site/base-site-information.php | 8 ++++---- .../Framework/Site/Post_Site_Information_Test.php | 4 ++++ .../Framework/Site/Term_Site_Information_Test.php | 2 ++ .../Framework/Site/Post_Site_Information_Test.php | 11 ++++++++++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/editors/framework/site/base-site-information.php b/src/editors/framework/site/base-site-information.php index f472e35bf15..417b50d96c9 100644 --- a/src/editors/framework/site/base-site-information.php +++ b/src/editors/framework/site/base-site-information.php @@ -92,8 +92,8 @@ public function get_site_information(): array { 'isPremium' => $this->product_helper->is_premium(), 'siteIconUrl' => \get_site_icon_url(), 'showSocial' => [ - 'facebook' => WPSEO_Options::get( 'opengraph', false ), - 'twitter' => WPSEO_Options::get( 'twitter', false ), + 'facebook' => $this->options_helper->get( 'opengraph', false ), + 'twitter' => $this->options_helper->get( 'twitter', false ), ], // phpcs:ignore Generic.ControlStructures.DisallowYodaConditions -- Bug: squizlabs/PHP_CodeSniffer#2962. 'isPrivateBlog' => ( (string) \get_option( 'blog_public' ) ) === '0', @@ -122,8 +122,8 @@ public function get_legacy_site_information(): array { 'isPremium' => $this->product_helper->is_premium(), 'siteIconUrl' => \get_site_icon_url(), 'showSocial' => [ - 'facebook' => WPSEO_Options::get( 'opengraph', false ), - 'twitter' => WPSEO_Options::get( 'twitter', false ), + 'facebook' => $this->options_helper->get( 'opengraph', false ), + 'twitter' => $this->options_helper->get( 'twitter', false ), ], ], ]; diff --git a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php index 0a466311339..635dc1d89dc 100644 --- a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php @@ -159,6 +159,8 @@ public function test_legacy_site_information() { $this->promotion_manager->expects( 'get_current_promotions' )->andReturn( [ 'the promotion', 'another one' ] ); $this->promotion_manager->expects( 'is' )->andReturnFalse(); $this->short_link_helper->expects( 'get' )->andReturn( 'https://expl.c' ); + $this->options_helper->expects( 'get' )->with( 'opengraph', false )->andReturn( false ); + $this->options_helper->expects( 'get' )->with( 'twitter', false )->andReturn( false ); $this->assertSame( $expected, $this->instance->get_legacy_site_information() ); } @@ -222,6 +224,8 @@ public function test_site_information() { $this->promotion_manager->expects( 'get_current_promotions' )->andReturn( [ 'the promotion', 'another one' ] ); $this->promotion_manager->expects( 'is' )->andReturnFalse(); $this->short_link_helper->expects( 'get' )->andReturn( 'https://expl.c' ); + $this->options_helper->expects( 'get' )->with( 'opengraph', false )->andReturn( false ); + $this->options_helper->expects( 'get' )->with( 'twitter', false )->andReturn( false ); $this->assertSame( $expected, $this->instance->get_site_information() ); } diff --git a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php index b8e3a16525e..ffcefc2827a 100644 --- a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php @@ -93,6 +93,8 @@ protected function set_up() { $this->instance->set_term( $mock_term ); $this->options_helper->expects( 'get' )->with( 'stripcategorybase', false )->andReturnFalse(); + $this->options_helper->expects( 'get' )->with( 'opengraph', false )->andReturn( false ); + $this->options_helper->expects( 'get' )->with( 'twitter', false )->andReturn( false ); $this->set_mocks(); } diff --git a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php index 7f8e5fceeaf..22d715a5a29 100644 --- a/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/WP/Editors/Framework/Site/Post_Site_Information_Test.php @@ -6,6 +6,7 @@ use Mockery; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; use Yoast\WP\SEO\Editors\Framework\Site\Post_Site_Information; +use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; @@ -64,6 +65,13 @@ final class Post_Site_Information_Test extends TestCase { */ private $product_helper; + /** + * The options helper. + * + * @var Mockery\MockInterface|Options_Helper $options_helper + */ + private $options_helper; + /** * The Post_Site_Information container. * @@ -84,9 +92,10 @@ public function set_up() { $this->wistia_embed_repo->expects( 'get_value_for_user' )->with( 0 )->andReturnTrue(); $this->meta_surface = \YoastSEO()->meta; $this->product_helper = \YoastSEO()->helpers->product; + $this->options_helper = \YoastSEO()->helpers->options; $this->alert_dismissal_action = \YoastSEO()->classes->get( Alert_Dismissal_Action::class ); - $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action ); + $this->instance = new Post_Site_Information( $this->promotion_manager, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->alert_dismissal_action, $this->options_helper ); $this->instance->set_permalink( 'perma' ); } From 23bb230bb3d2de5e52ad270989390634b0ae5275 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Mon, 16 Sep 2024 13:50:51 +0200 Subject: [PATCH 143/313] Add snapshot test for route-layout component. --- .../src/dashboard/components/route-layout.js | 2 +- .../dashboard/components/route-layout.test.js | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 packages/js/tests/dashboard/components/route-layout.test.js diff --git a/packages/js/src/dashboard/components/route-layout.js b/packages/js/src/dashboard/components/route-layout.js index a26be2330a6..bfa2a279905 100644 --- a/packages/js/src/dashboard/components/route-layout.js +++ b/packages/js/src/dashboard/components/route-layout.js @@ -11,7 +11,7 @@ import { LiveAnnouncer, LiveMessage } from "react-aria-live"; * @param {JSX.node} [description] The description. * @returns {JSX.Element} The route layout component. */ -const RouteLayout = ( { +export const RouteLayout = ( { children, title, description, diff --git a/packages/js/tests/dashboard/components/route-layout.test.js b/packages/js/tests/dashboard/components/route-layout.test.js new file mode 100644 index 00000000000..626a874c1a1 --- /dev/null +++ b/packages/js/tests/dashboard/components/route-layout.test.js @@ -0,0 +1,24 @@ +import { render } from "../../test-utils"; +import { RouteLayout } from "../../../src/dashboard/components/route-layout"; + +describe( "RouteLayout", () => { + const props = { + children: <>empty component , + title: "Title", + description: "Description", + }; + + it( "renders the component correctly for default props", () => { + const { container } = render( ); + expect( container ).toMatchSnapshot(); + } ); + + it( "renders the component correctly for custom props", () => { + const { container } = render( ); + expect( container ).toMatchSnapshot(); + } ); +} ); From 48a73fae853d350233b3587ef57e43750b4cd530 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 16 Sep 2024 15:37:40 +0300 Subject: [PATCH 144/313] refctor: adjust child classes after adding options-helper to the parent --- .../framework/site/base-site-information.php | 2 +- .../framework/site/post-site-information.php | 7 ---- .../framework/site/term-site-information.php | 33 ------------------- .../Site/Term_Site_Information_Test.php | 2 +- 4 files changed, 2 insertions(+), 42 deletions(-) diff --git a/src/editors/framework/site/base-site-information.php b/src/editors/framework/site/base-site-information.php index 417b50d96c9..20e9b1087fc 100644 --- a/src/editors/framework/site/base-site-information.php +++ b/src/editors/framework/site/base-site-information.php @@ -47,7 +47,7 @@ abstract class Base_Site_Information { * * @var Options_Helper $options_helper */ - protected $optios_helper; + protected $options_helper; /** * The constructor. diff --git a/src/editors/framework/site/post-site-information.php b/src/editors/framework/site/post-site-information.php index c6354dcc425..d8213d716d8 100644 --- a/src/editors/framework/site/post-site-information.php +++ b/src/editors/framework/site/post-site-information.php @@ -36,13 +36,6 @@ class Post_Site_Information extends Base_Site_Information { */ private $promotion_manager; - /** - * The options helper. - * - * @var Options_Helper $options_helper - */ - protected $options_helper; - /** * Constructs the class. * diff --git a/src/editors/framework/site/term-site-information.php b/src/editors/framework/site/term-site-information.php index f84989e92f7..532de69aa35 100644 --- a/src/editors/framework/site/term-site-information.php +++ b/src/editors/framework/site/term-site-information.php @@ -4,24 +4,12 @@ use WP_Taxonomy; use WP_Term; -use Yoast\WP\SEO\Helpers\Options_Helper; -use Yoast\WP\SEO\Helpers\Product_Helper; -use Yoast\WP\SEO\Helpers\Short_Link_Helper; -use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; -use Yoast\WP\SEO\Surfaces\Meta_Surface; /** * The Term_Site_Information class. */ class Term_Site_Information extends Base_Site_Information { - /** - * The options helper. - * - * @var Options_Helper - */ - protected $options_helper; - /** * The taxonomy. * @@ -36,27 +24,6 @@ class Term_Site_Information extends Base_Site_Information { */ private $term; - /** - * The constructor. - * - * @param Options_Helper $options_helper The options helper. - * @param Short_Link_Helper $short_link_helper The short link helper. - * @param Wistia_Embed_Permission_Repository $wistia_embed_permission_repository The wistia embed permission - * repository. - * @param Meta_Surface $meta The meta surface. - * @param Product_Helper $product_helper The product helper. - */ - public function __construct( - Options_Helper $options_helper, - Short_Link_Helper $short_link_helper, - Wistia_Embed_Permission_Repository $wistia_embed_permission_repository, - Meta_Surface $meta, - Product_Helper $product_helper - ) { - parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper, $options_helper ); - $this->options_helper = $options_helper; - } - /** * Sets the term for the information object and retrieves its taxonomy. * diff --git a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php index ffcefc2827a..8748e90aa8e 100644 --- a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php @@ -82,7 +82,7 @@ protected function set_up() { $this->meta_surface = Mockery::mock( Meta_Surface::class ); $this->product_helper = Mockery::mock( Product_Helper::class ); - $this->instance = new Term_Site_Information( $this->options_helper, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper ); + $this->instance = new Term_Site_Information( $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->options_helper ); $taxonomy = Mockery::mock( WP_Taxonomy::class )->makePartial(); $taxonomy->rewrite = false; $mock_term = Mockery::mock( WP_Term::class )->makePartial(); From db295e43e86ca42bd21cfdbbe9768a675697fa61 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 16 Sep 2024 15:37:40 +0300 Subject: [PATCH 145/313] refctor: adjust child classes after adding options-helper to the parent --- .../framework/site/base-site-information.php | 2 +- .../framework/site/post-site-information.php | 7 ---- .../framework/site/term-site-information.php | 33 ------------------- .../Site/Term_Site_Information_Test.php | 2 +- 4 files changed, 2 insertions(+), 42 deletions(-) diff --git a/src/editors/framework/site/base-site-information.php b/src/editors/framework/site/base-site-information.php index ecb202a6132..598e3341ff9 100644 --- a/src/editors/framework/site/base-site-information.php +++ b/src/editors/framework/site/base-site-information.php @@ -47,7 +47,7 @@ abstract class Base_Site_Information { * * @var Options_Helper $options_helper */ - protected $optios_helper; + protected $options_helper; /** * The constructor. diff --git a/src/editors/framework/site/post-site-information.php b/src/editors/framework/site/post-site-information.php index c6354dcc425..d8213d716d8 100644 --- a/src/editors/framework/site/post-site-information.php +++ b/src/editors/framework/site/post-site-information.php @@ -36,13 +36,6 @@ class Post_Site_Information extends Base_Site_Information { */ private $promotion_manager; - /** - * The options helper. - * - * @var Options_Helper $options_helper - */ - protected $options_helper; - /** * Constructs the class. * diff --git a/src/editors/framework/site/term-site-information.php b/src/editors/framework/site/term-site-information.php index f84989e92f7..532de69aa35 100644 --- a/src/editors/framework/site/term-site-information.php +++ b/src/editors/framework/site/term-site-information.php @@ -4,24 +4,12 @@ use WP_Taxonomy; use WP_Term; -use Yoast\WP\SEO\Helpers\Options_Helper; -use Yoast\WP\SEO\Helpers\Product_Helper; -use Yoast\WP\SEO\Helpers\Short_Link_Helper; -use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; -use Yoast\WP\SEO\Surfaces\Meta_Surface; /** * The Term_Site_Information class. */ class Term_Site_Information extends Base_Site_Information { - /** - * The options helper. - * - * @var Options_Helper - */ - protected $options_helper; - /** * The taxonomy. * @@ -36,27 +24,6 @@ class Term_Site_Information extends Base_Site_Information { */ private $term; - /** - * The constructor. - * - * @param Options_Helper $options_helper The options helper. - * @param Short_Link_Helper $short_link_helper The short link helper. - * @param Wistia_Embed_Permission_Repository $wistia_embed_permission_repository The wistia embed permission - * repository. - * @param Meta_Surface $meta The meta surface. - * @param Product_Helper $product_helper The product helper. - */ - public function __construct( - Options_Helper $options_helper, - Short_Link_Helper $short_link_helper, - Wistia_Embed_Permission_Repository $wistia_embed_permission_repository, - Meta_Surface $meta, - Product_Helper $product_helper - ) { - parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper, $options_helper ); - $this->options_helper = $options_helper; - } - /** * Sets the term for the information object and retrieves its taxonomy. * diff --git a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php index f104ea4c43a..30803ef68da 100644 --- a/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Term_Site_Information_Test.php @@ -82,7 +82,7 @@ protected function set_up() { $this->meta_surface = Mockery::mock( Meta_Surface::class ); $this->product_helper = Mockery::mock( Product_Helper::class ); - $this->instance = new Term_Site_Information( $this->options_helper, $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper ); + $this->instance = new Term_Site_Information( $this->short_link_helper, $this->wistia_embed_repo, $this->meta_surface, $this->product_helper, $this->options_helper ); $taxonomy = Mockery::mock( WP_Taxonomy::class )->makePartial(); $taxonomy->rewrite = false; $mock_term = Mockery::mock( WP_Term::class )->makePartial(); From 1b1a13db7a79487636b75f16d771d40222d27066 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Mon, 16 Sep 2024 16:08:03 +0300 Subject: [PATCH 146/313] tests: adds the new snpshot --- .../__snapshots__/route-layout.test.js.snap | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 packages/js/tests/dashboard/components/__snapshots__/route-layout.test.js.snap diff --git a/packages/js/tests/dashboard/components/__snapshots__/route-layout.test.js.snap b/packages/js/tests/dashboard/components/__snapshots__/route-layout.test.js.snap new file mode 100644 index 00000000000..2a8707c8899 --- /dev/null +++ b/packages/js/tests/dashboard/components/__snapshots__/route-layout.test.js.snap @@ -0,0 +1,97 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RouteLayout renders the component correctly for custom props 1`] = ` +
+
+
+

+ Custom title +

+

+ custom description +

+
+
+ empty component +
+
+
+
+ Custom title Dashboard - Yoast SEO +
+
+
+
+`; + +exports[`RouteLayout renders the component correctly for default props 1`] = ` +
+
+
+

+ Title +

+

+ Description +

+
+
+ empty component +
+
+
+
+ Title Dashboard - Yoast SEO +
+
+
+
+`; From f0780bfe88803052f7e7b27a7ef02f22b5e260ce Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Mon, 16 Sep 2024 15:18:02 +0200 Subject: [PATCH 147/313] Move down. --- .../Unit/Editors/Framework/Site/Post_Site_Information_Test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php index 6177054b4f6..50dc92342b9 100644 --- a/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php +++ b/tests/Unit/Editors/Framework/Site/Post_Site_Information_Test.php @@ -209,11 +209,11 @@ public function test_site_information() { 'isRtl' => false, 'isPremium' => true, 'siteIconUrl' => 'https://example.org', - 'sitewideSocialImage' => null, 'showSocial' => [ 'facebook' => false, 'twitter' => false, ], + 'sitewideSocialImage' => null, 'isPrivateBlog' => false, ]; From 64fc7f532c8ac7fafad3929b409556cfe2d332c0 Mon Sep 17 00:00:00 2001 From: Martijn van der Klis Date: Tue, 17 Sep 2024 08:51:32 +0200 Subject: [PATCH 148/313] Applies suggested JSDoc changes Co-authored-by: Aida Marfuaty <48715883+FAMarfuaty@users.noreply.github.com> --- packages/yoastseo/src/app.js | 8 +-- .../bundledPlugins/previouslyUsedKeywords.js | 4 +- packages/yoastseo/src/helpers/factory.js | 39 +++++++------- .../helpers/syllables/DeviationFragment.js | 2 +- .../helpers/syllables/syllableCountStep.js | 6 +-- .../src/scoring/assessors/assessor.js | 4 +- .../scoring/renderers/AssessorPresenter.js | 12 ++--- .../yoastseo/src/values/AssessmentResult.js | 29 +++++++---- packages/yoastseo/src/values/Mark.js | 10 ++-- packages/yoastseo/src/values/Paper.js | 52 +++++++++---------- 10 files changed, 87 insertions(+), 79 deletions(-) diff --git a/packages/yoastseo/src/app.js b/packages/yoastseo/src/app.js index e0a49e7eb36..99c54bdf2d4 100644 --- a/packages/yoastseo/src/app.js +++ b/packages/yoastseo/src/app.js @@ -294,7 +294,7 @@ class App { } /** - * Initializes assessors based on if the respective analysis is active. + * Initializes assessors based on whether the respective analysis is active. * * @param {Object} args The arguments passed to the App. * @returns {void} @@ -349,7 +349,7 @@ class App { } /** - * Extend the config with defaults. + * Extends the config with defaults. * * @param {Object} args The arguments to be extended. * @returns {Object} args The extended arguments. @@ -362,7 +362,7 @@ class App { } /** - * Extend sample text config with defaults. + * Extends sample text config with defaults. * * @param {Object} sampleText The sample text to be extended. * @returns {Object} sampleText The extended sample text. @@ -610,7 +610,7 @@ class App { } /** - * Function to fire the analyzer when all plugins are loaded, removes the loading dialog. + * Removes the loading dialog and fires the analyzer when all plugins are loaded. * * @returns {void} */ diff --git a/packages/yoastseo/src/bundledPlugins/previouslyUsedKeywords.js b/packages/yoastseo/src/bundledPlugins/previouslyUsedKeywords.js index 7c61e17f4c1..f0dfb3ca13d 100644 --- a/packages/yoastseo/src/bundledPlugins/previouslyUsedKeywords.js +++ b/packages/yoastseo/src/bundledPlugins/previouslyUsedKeywords.js @@ -174,10 +174,10 @@ export default class PreviouslyUsedKeyword { } /** - * The assessment for the previously used keywords. + * Executes the assessment that checks whether a text uses previously used keywords. * * @param {Paper} paper The Paper object to assess. - * @returns {AssessmentResult} The assessment result of the assessment + * @returns {AssessmentResult} The assessment result containing both a score and a descriptive text. */ assess( paper ) { const previouslyUsedKeywords = this.researchPreviouslyUsedKeywords( paper ); diff --git a/packages/yoastseo/src/helpers/factory.js b/packages/yoastseo/src/helpers/factory.js index 1127de7fd93..97e7b8ea1ae 100644 --- a/packages/yoastseo/src/helpers/factory.js +++ b/packages/yoastseo/src/helpers/factory.js @@ -3,7 +3,6 @@ import { isUndefined } from "lodash"; /** * FactoryProto is a mock factory function. * - * @returns {void} */ export default class FactoryProto { /** @@ -19,13 +18,13 @@ export default class FactoryProto { } /** - * Returns a mock researcher + * Returns a mock researcher. * - * @param {object} expectedValue The expected value or values. - * @param {boolean} multiValue True if multiple values are expected. - * @param {boolean} hasMorphologyData True if the researcher has access to morphology data. - * @param {Object|boolean} config Optional config to be used for an assessment. - * @param {Object|boolean} helpers Optional helpers to be used for an assessment. + * @param {Object} expectedValue Expected value. + * @param {boolean} [multiValue=false] Whether the researcher has multiple values. + * @param {boolean} [hasMorphologyData=false] Whether the researcher has morphology data. + * @param {Object} [config=false] Optional config to be used for an assessment. + * @param {Object} [helpers=false] Optional helpers to be used for an assessment. * * @returns {Researcher} Mock researcher. */ @@ -34,7 +33,7 @@ export default class FactoryProto { if ( multiValue && ( typeof expectedValue === "object" || typeof helpers === "object" || typeof config === "object" ) ) { return { /** - * Return research results by research name for multi-value mock researchers. + * Returns research results by research name for multi-value mock researches. * * @param {string} research The name of the research. * @@ -45,7 +44,7 @@ export default class FactoryProto { }, /** - * Return whether the worker has the research. + * Returns whether the worker has the specified research. * @param {string} research The name of the research. * @returns {boolean} Whether the worker has the research. */ @@ -65,7 +64,7 @@ export default class FactoryProto { }, /** - * Check whether morphology data is available. + * Checks whether morphology data is available. * * @returns {boolean} True if the researcher has access to morphology data. */ @@ -74,7 +73,7 @@ export default class FactoryProto { }, /** - * Return the helper to be used for the assessment. + * Returns the helper to be used for the assessment. * @param {string} name The name of the helper. * * @returns {function} The helper for the assessment. @@ -108,7 +107,7 @@ export default class FactoryProto { }, /** - * Return the config to be used for the assessment. + * Returns the config to be used for the assessment. * @param {string} name The name of the config. * * @returns {function} The config for the assessment. @@ -141,7 +140,7 @@ export default class FactoryProto { } return { /** - * Return research results. + * Returns research results. * * @returns {Object} The results of the research. */ @@ -159,7 +158,7 @@ export default class FactoryProto { }, /** - * Return the helpers to be used for the assessment. + * Returns the helpers to be used for the assessment. * * @returns {Object} The helpers for the assessment. */ @@ -168,7 +167,7 @@ export default class FactoryProto { }, /** - * Return whether the worker has the helper. + * Returns whether the worker has the specified helper. * * @returns {boolean} Whether the worker has the helper. */ @@ -177,7 +176,7 @@ export default class FactoryProto { }, /** - * Return the config to be used for the assessment. + * Returns the config to be used for the assessment. * * @returns {Object} The config for the assessment results. */ @@ -186,12 +185,12 @@ export default class FactoryProto { }, /** - * Return whether the worker has the config. + * Returns whether the worker has the specified config. * @param {string} research The name of the config. - * @returns {boolean} Whether the worker has the research. + * @returns {boolean} Whether the worker has the specified config. */ - hasConfig: function( research ) { - return ! isUndefined( expectedValue[ research ] ); + hasConfig: function( config ) { + return ! isUndefined( expectedValue[ config ] ); }, }; } diff --git a/packages/yoastseo/src/languageProcessing/helpers/syllables/DeviationFragment.js b/packages/yoastseo/src/languageProcessing/helpers/syllables/DeviationFragment.js index 1b8b9f24422..1997f19edd3 100644 --- a/packages/yoastseo/src/languageProcessing/helpers/syllables/DeviationFragment.js +++ b/packages/yoastseo/src/languageProcessing/helpers/syllables/DeviationFragment.js @@ -7,7 +7,7 @@ export default class DeviationFragment { /** * Constructs a new DeviationFragment. * - * @param {Object} options Extra options about how to match this fragment. + * @param {Object} options Extra options that are used to match this deviation fragment. * @param {string} options.location The location in the word where this deviation can occur. * @param {string} options.word The actual string that should be counted differently. * @param {number} options.syllables The amount of syllables this fragment has. diff --git a/packages/yoastseo/src/languageProcessing/helpers/syllables/syllableCountStep.js b/packages/yoastseo/src/languageProcessing/helpers/syllables/syllableCountStep.js index c9c11cdabc4..c7bbf6f5c9f 100644 --- a/packages/yoastseo/src/languageProcessing/helpers/syllables/syllableCountStep.js +++ b/packages/yoastseo/src/languageProcessing/helpers/syllables/syllableCountStep.js @@ -20,7 +20,7 @@ export default class SyllableCountStep { } /** - * Returns if a valid regex has been set. + * Checks whether a valid regex has been set. * * @returns {boolean} True if a regex has been set, false if not. */ @@ -52,8 +52,8 @@ export default class SyllableCountStep { } /** - * Matches syllable exclusions in a given word and the returns the number found multiplied with the - * given multiplier. + * Matches syllable exclusions in a given word and returns the number found multiplied by the + * given multiplier. The result of this multiplication is the syllable count. * * @param {String} word The word to match for syllable exclusions. * @returns {number} The amount of syllables found. diff --git a/packages/yoastseo/src/scoring/assessors/assessor.js b/packages/yoastseo/src/scoring/assessors/assessor.js index 23862566234..9ac6df9a097 100644 --- a/packages/yoastseo/src/scoring/assessors/assessor.js +++ b/packages/yoastseo/src/scoring/assessors/assessor.js @@ -238,7 +238,7 @@ class Assessor { } /** - * Register an assessment to add it to the internal assessments object. + * Registers an assessment and adds it to the internal assessments object. * * @param {string} name The name of the assessment. * @param {Assessment} assessment The object containing function to run as an assessment and it's requirements. @@ -258,7 +258,7 @@ class Assessor { } /** - * Remove a specific Assessment from the list of Assessments. + * Removes a specific Assessment from the list of Assessments. * * @param {string} name The Assessment to remove from the list of assessments. * @returns {void} diff --git a/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js b/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js index 6f91f3d6e1d..8afbb46282b 100644 --- a/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js +++ b/packages/yoastseo/src/scoring/renderers/AssessorPresenter.js @@ -80,7 +80,7 @@ class AssessorPresenter { } /** - * Get the indicator screen reader text from the presenter configuration, if it exists. + * Gets the indicator screen reader text from the presenter configuration, if it exists. * * @param {string} rating The rating to check against the config. * @returns {string} Translated string containing the screen reader text to be used. @@ -94,7 +94,7 @@ class AssessorPresenter { } /** - * Get the indicator screen reader readability text from the presenter configuration, if it exists. + * Gets the indicator screen reader readability text from the presenter configuration, if it exists. * * @param {string} rating The rating to check against the config. * @returns {string} Translated string containing the screen reader readability text to be used. @@ -108,7 +108,7 @@ class AssessorPresenter { } /** - * Get the indicator full text from the presenter configuration, if it exists. + * Gets the indicator full text from the presenter configuration, if it exists. * * @param {string} rating The rating to check against the config. * @returns {string} Translated string containing the full text to be used. @@ -233,7 +233,7 @@ class AssessorPresenter { } /** - * Mark with a given marker. This will set the active marker to the correct value. + * Marks with a given marker. This will set the active marker to the correct value. * * @param {string} identifier The identifier for the assessment/marker. * @param {Function} marker The marker function. @@ -306,7 +306,7 @@ class AssessorPresenter { } /** - * Adds event handlers to the mark buttons + * Adds event handlers to the mark buttons. * * @param {Object} scores The list of rendered scores. * @@ -322,7 +322,7 @@ class AssessorPresenter { } /** - * Removes all marks currently on the text + * Removes all marks currently on the text. * * @returns {void} */ diff --git a/packages/yoastseo/src/values/AssessmentResult.js b/packages/yoastseo/src/values/AssessmentResult.js index ee893d99d1f..8a250e04c9d 100644 --- a/packages/yoastseo/src/values/AssessmentResult.js +++ b/packages/yoastseo/src/values/AssessmentResult.js @@ -17,6 +17,15 @@ class AssessmentResult { * Constructs the AssessmentResult value object. * * @param {Object} [values] The values for this assessment result. + * @param {number} [values.score] The score for this assessment result. + * @param {string} [values.text] The text for this assessment result. This is the text that can be used as a feedback message associated with the score. + * @param {array} [values.marks] The marks for this assessment result. + * @param {boolean} [values._hasBetaBadge] Whether this result has a beta badge. + * @param {boolean} [values._hasJumps] Whether this result causes a jump to a different field. + * @param {string} [values.editFieldName] The edit field name for this assessment result. + * @param {boolean} [values._hasAIFixes] Whether this result has AI fixes. + * @constructor + * @returns {void} */ constructor( values ) { this._hasScore = false; @@ -74,7 +83,7 @@ class AssessmentResult { } /** - * Gets the available score + * Gets the available score. * @returns {number} The score associated with the AssessmentResult. */ getScore() { @@ -83,7 +92,7 @@ class AssessmentResult { /** * Sets the score for the assessment. - * @param {number} score The score to be used for the score property + * @param {number} score The score to be used for the score property. * @returns {void} */ setScore( score ) { @@ -94,7 +103,7 @@ class AssessmentResult { } /** - * Checks if a text is available. + * Checks if a text for the assessment result is available. * @returns {boolean} Whether or not a text is available. */ hasText() { @@ -102,7 +111,7 @@ class AssessmentResult { } /** - * Gets the available text + * Gets the available text for the assessment result. * @returns {string} The text associated with the AssessmentResult. */ getText() { @@ -111,7 +120,7 @@ class AssessmentResult { /** * Sets the text for the assessment. - * @param {string} text The text to be used for the text property + * @param {string} text The text to be used for the text property. * @returns {void} */ setText( text ) { @@ -134,7 +143,7 @@ class AssessmentResult { /** * Sets the marks for the assessment. * - * @param {array} marks The marks to be used for the marks property + * @param {array} marks The marks to be used for the marks property. * * @returns {void} */ @@ -146,7 +155,7 @@ class AssessmentResult { } /** - * Sets the identifier + * Sets the identifier. * * @param {string} identifier An alphanumeric identifier for this result. * @returns {void} @@ -156,7 +165,7 @@ class AssessmentResult { } /** - * Gets the identifier + * Gets the identifier. * * @returns {string} An alphanumeric identifier for this result. */ @@ -165,7 +174,7 @@ class AssessmentResult { } /** - * Sets the marker, a pure function that can return the marks for a given Paper + * Sets the marker, a pure function that can return the marks for a given Paper. * * @param {Function} marker The marker to set. * @returns {void} @@ -184,7 +193,7 @@ class AssessmentResult { } /** - * Gets the marker, a pure function that can return the marks for a given Paper + * Gets the marker, a pure function that can return the marks for a given Paper. * * @returns {Function} The marker. */ diff --git a/packages/yoastseo/src/values/Mark.js b/packages/yoastseo/src/values/Mark.js index 89564171df9..7a47717f4d0 100644 --- a/packages/yoastseo/src/values/Mark.js +++ b/packages/yoastseo/src/values/Mark.js @@ -13,11 +13,11 @@ class Mark { * * @param {Object} [properties] The properties of this Mark. * - * @param {string?} properties.original The original text that should be marked. - * @param {string?} properties.marked The new text including marks. - * @param {array?} properties.fieldsToMark The array that specifies which text section(s) to mark. + * @param {string} [properties.original] The original text that should be marked. + * @param {string} [properties.marked] The new text including marks. + * @param {array} [properties.fieldsToMark] The array that specifies which text section(s) to mark, e.g. "heading". * - * @param {SourceCodeRange?} properties.position The position object: a range in the source code. + * @param {SourceCodeRange} [properties.position] The position object: a range in the source code. */ constructor( properties ) { properties = properties || {}; @@ -157,7 +157,7 @@ class Mark { /** * Returns the start position inside block. * - * @returns {number} The start position inside block if the mark position information, undefined otherwise. + * @returns {number} The start position inside the block if the mark has position information, undefined otherwise. */ getBlockPositionStart() { return this._properties.position && this._properties.position.startOffsetBlock; diff --git a/packages/yoastseo/src/values/Paper.js b/packages/yoastseo/src/values/Paper.js index 85a13a46fc4..e6b1e5a79fc 100644 --- a/packages/yoastseo/src/values/Paper.js +++ b/packages/yoastseo/src/values/Paper.js @@ -26,23 +26,23 @@ const defaultAttributes = { */ class Paper { /** - * Constructs the Paper object and sets the keyword property. + * Constructs the Paper object and sets its attributes. * * @param {string} text The text to use in the analysis. * @param {object} [attributes] The object containing all attributes. - * @param {string} [attributes.keyword] The main keyword. - * @param {string} [attributes.synonyms] The main keyword's synonyms. - * @param {string} [attributes.description] The SEO description. + * @param {string} [attributes.keyword] The main keyword or keyphrase of the text. + * @param {string} [attributes.synonyms] The synonyms of the main keyword or keyphrase. It should be separated by commas if multiple synonyms are added. + * @param {string} [attributes.description] The SEO meta description. * @param {string} [attributes.title] The SEO title. - * @param {number} [attributes.titleWidth] The width of the title in pixels. + * @param {number} [attributes.titleWidth=0] The width of the title in pixels. * @param {string} [attributes.slug] The slug. - * @param {string} [attributes.locale] The locale. - * @param {string} [attributes.permalink] The base url + slug. + * @param {string} [attributes.locale=en_US] The locale. + * @param {string} [attributes.permalink] The full URL for any given post, page, or other pieces of content on a site. * @param {string} [attributes.date] The date. - * @param {Object} [attributes.wpBlocks] The text, encoded in WordPress block editor blocks. + * @param {Object[]} [attributes.wpBlocks] The array of texts, encoded in WordPress block editor blocks. * @param {Object} [attributes.customData] Custom data. * @param {string} [attributes.textTitle] The title of the text. - * @param {string} [attributes.writingDirection] The writing direction of the paper. Defaults to left to right (LTR). + * @param {string} [attributes.writingDirection=LTR] The writing direction of the paper. Defaults to left to right (LTR). */ constructor( text, attributes ) { this._text = text || ""; @@ -113,8 +113,8 @@ class Paper { } /** - * Returns the associated text or am empty string if no text is available. - * @returns {string} Returns text + * Returns the associated text or an empty string if no text is available. + * @returns {string} Returns the text. */ getText() { return this._text; @@ -157,39 +157,39 @@ class Paper { } /** - * Checks whether a title is available - * @returns {boolean} Returns true if the Paper has a title. + * Checks whether an SEO title is available + * @returns {boolean} Returns true if the Paper has an SEO title. */ hasTitle() { return this._attributes.title !== ""; } /** - * Returns the title, or an empty string of no title is available. - * @returns {string} Returns the title + * Returns the SEO title, or an empty string if no title is available. + * @returns {string} Returns the SEO title. */ getTitle() { return this._attributes.title; } /** - * Checks whether a title width in pixels is available - * @returns {boolean} Returns true if the Paper has a title. + * Checks whether an SEO title width in pixels is available. + * @returns {boolean} Returns true if the Paper's SEO title is wider than 0 pixels. */ hasTitleWidth() { return this._attributes.titleWidth !== 0; } /** - * Returns the title width in pixels, or an empty string of no title width in pixels is available. - * @returns {number} Returns the title + * Gets the SEO title width in pixels, or an empty string of no title width in pixels is available. + * @returns {number} Returns the SEO title width in pixels. */ getTitleWidth() { return this._attributes.titleWidth; } /** - * Checks whether a slug is available + * Checks whether a slug is available. * @returns {boolean} Returns true if the Paper has a slug. */ hasSlug() { @@ -197,8 +197,8 @@ class Paper { } /** - * Returns the slug, or an empty string of no slug is available. - * @returns {string} Returns the url + * Gets the paper's slug, or an empty string if no slug is available. + * @returns {string} Returns the slug. */ getSlug() { return this._attributes.slug; @@ -225,8 +225,8 @@ class Paper { } /** - * Checks whether a locale is available - * @returns {boolean} Returns true if the paper has a locale + * Checks whether a locale is available. + * @returns {boolean} Returns true if the paper has a locale. */ hasLocale() { return this._attributes.locale !== ""; @@ -234,7 +234,7 @@ class Paper { /** * Returns the locale or an empty string if no locale is available - * @returns {string} Returns the locale + * @returns {string} Returns the locale. */ getLocale() { return this._attributes.locale; @@ -251,7 +251,7 @@ class Paper { } /** - * Checks whether a permalink is available + * Checks whether a permalink is available. * @returns {boolean} Returns true if the Paper has a permalink. */ hasPermalink() { From fada9dd4cc0052d24c8fa4360eaf1006f74c3b14 Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Tue, 17 Sep 2024 10:55:15 +0300 Subject: [PATCH 149/313] Make new dashboard page the root of the Yoast SEO menu --- admin/menu/class-admin-menu.php | 6 +++++- .../user-interface/new-dashboard-page-integration.php | 10 ++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/admin/menu/class-admin-menu.php b/admin/menu/class-admin-menu.php index 5c5dce746e6..e8f5ba2a018 100644 --- a/admin/menu/class-admin-menu.php +++ b/admin/menu/class-admin-menu.php @@ -5,6 +5,7 @@ * @package WPSEO\Admin\Menu */ +use Yoast\WP\SEO\Conditionals\New_Dashboard_Ui_Conditional; /** * Registers the admin menu on the left of the admin area. */ @@ -88,7 +89,6 @@ public function get_submenu_pages() { // Submenu pages. $submenu_pages = [ - $this->get_submenu_page( __( 'General', 'wordpress-seo' ), $this->get_page_identifier() ), $this->get_submenu_page( __( 'Search Console', 'wordpress-seo' ), 'wpseo_search_console', @@ -98,6 +98,10 @@ public function get_submenu_pages() { $this->get_submenu_page( $this->get_license_page_title(), 'wpseo_licenses' ), ]; + if ( ! ( new New_Dashboard_Ui_Conditional() )->is_met() ) { + array_unshift( $submenu_pages, $this->get_submenu_page( __( 'General', 'wordpress-seo' ), $this->get_page_identifier() ) ); + } + /** * Filter: 'wpseo_submenu_pages' - Collects all submenus that need to be shown. * diff --git a/src/dashboard/user-interface/new-dashboard-page-integration.php b/src/dashboard/user-interface/new-dashboard-page-integration.php index adc05a50a06..9ddcb604197 100644 --- a/src/dashboard/user-interface/new-dashboard-page-integration.php +++ b/src/dashboard/user-interface/new-dashboard-page-integration.php @@ -17,10 +17,8 @@ class New_Dashboard_Page_Integration implements Integration_Interface { /** * The page name. - * - * @TODO RENAME THIS TO SOMETHING SENSIBLE AFTER FEATURE FLAG PERIOD */ - public const PAGE = 'wpseo_page_new_dashboard'; + public const PAGE = 'wpseo_dashboard'; /** * Holds the WPSEO_Admin_Asset_Manager. @@ -106,13 +104,13 @@ public function register_hooks() { public function add_page( $pages ) { \array_splice( $pages, - 3, + 0, 0, [ [ - 'wpseo_dashboard', + self::PAGE, '', - \__( 'New dashboard', 'wordpress-seo' ), + \__( 'General', 'wordpress-seo' ), 'wpseo_manage_options', self::PAGE, [ $this, 'display_page' ], From 0640e14bbf825f18be85ed394c609824276c98ca Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Tue, 17 Sep 2024 11:20:17 +0300 Subject: [PATCH 150/313] Fix unit tests --- .../New_Dashboard_Page_Integration_Test.php | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php index a6f98f9a376..1a449fd6baf 100644 --- a/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php +++ b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php @@ -127,7 +127,7 @@ public static function register_hooks_provider() { 'action_times' => 0, ], 'On dashboard page' => [ - 'current_page' => 'wpseo_page_new_dashboard', + 'current_page' => 'wpseo_dashboard', 'action_times' => 1, ], ]; @@ -173,19 +173,25 @@ public function test_register_hooks_on_dashboard_page( $current_page, $action_ti public function test_add_page() { $pages = $this->instance->add_page( [ - [ 'page1', '', 'Page 1', 'manage_options', 'page1', [ $this, 'display_page' ] ], - [ 'page2', '', 'Page 2', 'manage_options', 'page2', [ $this, 'display_page' ] ], - [ 'page3', '', 'Page 3', 'manage_options', 'page3', [ $this, 'display_page' ] ], + [ 'page1', '', 'Page 1', 'manage_options', 'page1', [ 'custom_display_page' ] ], + [ 'page2', '', 'Page 2', 'manage_options', 'page2', [ 'custom_display_page' ] ], + [ 'page3', '', 'Page 3', 'manage_options', 'page3', [ 'custom_display_page' ] ], ] ); - // Assert that the new page was added at index 3. - $this->assertEquals( 'wpseo_dashboard', $pages[3][0] ); + // Assert that the new page was added at index 0. + $this->assertEquals( 'wpseo_dashboard', $pages[0][0] ); + $this->assertEquals( 'page3', $pages[3][0] ); + $this->assertEquals( '', $pages[0][1] ); $this->assertEquals( '', $pages[3][1] ); - $this->assertEquals( 'New dashboard', $pages[3][2] ); - $this->assertEquals( 'wpseo_manage_options', $pages[3][3] ); - $this->assertEquals( 'wpseo_page_new_dashboard', $pages[3][4] ); - $this->assertEquals( [ $this->instance, 'display_page' ], $pages[3][5] ); + $this->assertEquals( 'General', $pages[0][2] ); + $this->assertEquals( 'Page 3', $pages[3][2] ); + $this->assertEquals( 'wpseo_manage_options', $pages[0][3] ); + $this->assertEquals( 'manage_options', $pages[3][3] ); + $this->assertEquals( 'wpseo_dashboard', $pages[0][4] ); + $this->assertEquals( 'page3', $pages[3][4] ); + $this->assertEquals( [ $this->instance, 'display_page' ], $pages[0][5] ); + $this->assertEquals( [ 'custom_display_page' ], $pages[3][5] ); } /** From 21c038ee81dded77bb76b1902aa1930c32b3c1d2 Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Tue, 17 Sep 2024 13:55:24 +0300 Subject: [PATCH 151/313] Fix CSS that broke due to page change --- css/src/new-dashboard.css | 2 +- .../User_Interface/New_Dashboard_Page_Integration_Test.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/css/src/new-dashboard.css b/css/src/new-dashboard.css index 68e7a5cbf77..83638003123 100644 --- a/css/src/new-dashboard.css +++ b/css/src/new-dashboard.css @@ -1,4 +1,4 @@ -body.seo_page_wpseo_page_new_dashboard { +body.toplevel_page_wpseo_dashboard { @apply yst-bg-slate-100; /* Move WP footer behind our content. */ diff --git a/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php index 1a449fd6baf..ef377978bdc 100644 --- a/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php +++ b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php @@ -21,7 +21,7 @@ */ final class New_Dashboard_Page_Integration_Test extends TestCase { - public const PAGE = 'wpseo_page_new_dashboard'; + public const PAGE = 'wpseo_dashboard'; /** * Holds the WPSEO_Admin_Asset_Manager. From 74324e982a77ebd7fe64ed215d91d5d2f6ebff1c Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Tue, 17 Sep 2024 14:57:24 +0300 Subject: [PATCH 152/313] Stop showing non-Yoast notices on the new dashboard --- css/src/new-dashboard.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/css/src/new-dashboard.css b/css/src/new-dashboard.css index 83638003123..1a7492bf206 100644 --- a/css/src/new-dashboard.css +++ b/css/src/new-dashboard.css @@ -1,4 +1,8 @@ body.toplevel_page_wpseo_dashboard { + .notice:not(.notice-yoast) { + display:none; + } + @apply yst-bg-slate-100; /* Move WP footer behind our content. */ From 4c87344c7986f2f2336aeaf63f7cead9755049bb Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Tue, 17 Sep 2024 15:44:15 +0300 Subject: [PATCH 153/313] feat: added paper inside the components Removed changes to grid for sidebar recommendation because it should be under alert center component remove unused value --- packages/js/src/dashboard/app.js | 39 ++++--------------- packages/js/src/dashboard/components/index.js | 2 + .../src/dashboard/components/notifications.js | 12 ++++++ .../js/src/dashboard/components/problems.js | 12 ++++++ .../js/src/dashboard/routes/alert-center.js | 25 ++++++++++++ .../routes/first-time-configuration.js | 23 +++++------ packages/js/src/dashboard/routes/index.js | 1 + 7 files changed, 72 insertions(+), 42 deletions(-) create mode 100644 packages/js/src/dashboard/components/notifications.js create mode 100644 packages/js/src/dashboard/components/problems.js create mode 100644 packages/js/src/dashboard/routes/alert-center.js diff --git a/packages/js/src/dashboard/app.js b/packages/js/src/dashboard/app.js index 22d9da54941..638ada39390 100644 --- a/packages/js/src/dashboard/app.js +++ b/packages/js/src/dashboard/app.js @@ -3,13 +3,12 @@ import { Transition } from "@headlessui/react"; import { AdjustmentsIcon, BellIcon } from "@heroicons/react/outline"; import { __ } from "@wordpress/i18n"; -import { Paper, SidebarNavigation, Title, useSvgAria } from "@yoast/ui-library"; -import classNames from "classnames"; +import { SidebarNavigation, useSvgAria } from "@yoast/ui-library"; import PropTypes from "prop-types"; import { Link, Route, Routes, useLocation } from "react-router-dom"; import { MenuItemLink, YoastLogo } from "../shared-admin/components"; import { useSelectDashboard } from "./hooks"; -import { FirstTimeConfiguration } from "./routes"; +import { FirstTimeConfiguration, AlertCenter } from "./routes"; /** * @param {string} [idSuffix] Extra id suffix. Can prevent double IDs on the page. @@ -45,34 +44,12 @@ const Menu = ( { idSuffix = "" } ) => { Menu.propTypes = { idSuffix: PropTypes.string, }; -/** - * @returns {JSX.Element} The dashboard content placeholder. - */ -const Content = () => { - return <> -
-
- { __( "Alert center", "wordpress-seo" ) } -

- { __( "Monitor and manage potential SEO problems affecting your site and stay informed with important notifications and updates.", "wordpress-seo" ) } -

-
-
-
-
- Content -
-
- ; -}; + /** * @returns {JSX.Element} The app component. */ const App = () => { const { pathname } = useLocation(); - const isPremium = useSelectDashboard( "selectPreference", [], "isPremium" ); return ( { -
-
- +
+
+
{ enterTo="yst-opacity-100" > - } /> + } /> } /> - +
diff --git a/packages/js/src/dashboard/components/index.js b/packages/js/src/dashboard/components/index.js index d45a7ef5a64..2ff77285d50 100644 --- a/packages/js/src/dashboard/components/index.js +++ b/packages/js/src/dashboard/components/index.js @@ -1 +1,3 @@ export { default as RouteLayout } from "./route-layout"; +export { Notifications } from "./notifications"; +export { Problems } from "./problems"; diff --git a/packages/js/src/dashboard/components/notifications.js b/packages/js/src/dashboard/components/notifications.js new file mode 100644 index 00000000000..9bb3cc9006d --- /dev/null +++ b/packages/js/src/dashboard/components/notifications.js @@ -0,0 +1,12 @@ +import { Paper, Title } from "@yoast/ui-library"; + +/** + * @returns {JSX.Element} The notifications component. + */ +export const Notifications = () => { + return ( + + Notifications + + ); +}; diff --git a/packages/js/src/dashboard/components/problems.js b/packages/js/src/dashboard/components/problems.js new file mode 100644 index 00000000000..445e8594b8a --- /dev/null +++ b/packages/js/src/dashboard/components/problems.js @@ -0,0 +1,12 @@ +import { Paper, Title } from "@yoast/ui-library"; + +/** + * @returns {JSX.Element} The problems component. + */ +export const Problems = () => { + return ( + + Problems + + ); +}; diff --git a/packages/js/src/dashboard/routes/alert-center.js b/packages/js/src/dashboard/routes/alert-center.js new file mode 100644 index 00000000000..bd2ca116ada --- /dev/null +++ b/packages/js/src/dashboard/routes/alert-center.js @@ -0,0 +1,25 @@ +import { __ } from "@wordpress/i18n"; +import { Paper, Title } from "@yoast/ui-library"; +import { Notifications, Problems } from "../components"; + +/** + * @returns {JSX.Element} The dashboard content placeholder. + */ +export const AlertCenter = () => { + return <> + +
+
+ { __( "Alert center", "wordpress-seo" ) } +

+ { __( "Monitor and manage potential SEO problems affecting your site and stay informed with important notifications and updates.", "wordpress-seo" ) } +

+
+
+
+
+ + +
+ ; +}; diff --git a/packages/js/src/dashboard/routes/first-time-configuration.js b/packages/js/src/dashboard/routes/first-time-configuration.js index f4f7482f7e1..df34a9c28e5 100644 --- a/packages/js/src/dashboard/routes/first-time-configuration.js +++ b/packages/js/src/dashboard/routes/first-time-configuration.js @@ -1,8 +1,7 @@ import { __ } from "@wordpress/i18n"; - +import { Paper } from "@yoast/ui-library"; import FirstTimeConfigurationSteps from "../../first-time-configuration/first-time-configuration-steps"; import { - RouteLayout, } from "../components"; @@ -11,15 +10,17 @@ import { */ const FirstTimeConfiguration = () => { return ( - -
-
- -
-
+ + +
+
+ +
+
+
); }; diff --git a/packages/js/src/dashboard/routes/index.js b/packages/js/src/dashboard/routes/index.js index 6efe3db4442..6e02eefd69c 100644 --- a/packages/js/src/dashboard/routes/index.js +++ b/packages/js/src/dashboard/routes/index.js @@ -1,2 +1,3 @@ export { default as FirstTimeConfiguration } from "./first-time-configuration"; +export { AlertCenter } from "./alert-center"; From 4a5ca225786e846814886de961caed1bf5433d9c Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Tue, 17 Sep 2024 14:56:36 +0200 Subject: [PATCH 154/313] First progress adding sidebar and upsells. --- admin/class-config.php | 4 +- packages/js/src/dashboard/app.js | 12 ++++ .../components/sidebar-recommendations.js | 25 +++++++ packages/js/src/settings/app.js | 72 ++++--------------- .../components/premium-upsell-list.js | 67 +++++++++++++++++ .../new-dashboard-page-integration.php | 5 +- 6 files changed, 123 insertions(+), 62 deletions(-) create mode 100644 packages/js/src/dashboard/components/sidebar-recommendations.js create mode 100644 packages/js/src/shared-admin/components/premium-upsell-list.js diff --git a/admin/class-config.php b/admin/class-config.php index f8367968ae7..c47419ccebe 100644 --- a/admin/class-config.php +++ b/admin/class-config.php @@ -50,7 +50,7 @@ public function __construct() { public function init() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; - if ( in_array( $page, [ Settings_Integration::PAGE, Academy_Integration::PAGE, Support_Integration::PAGE ], true ) ) { + if ( in_array( $page, [ Settings_Integration::PAGE, Academy_Integration::PAGE, Support_Integration::PAGE, New_Dashboard_Page_Integration::PAGE,], true ) ) { // Bail, this is managed in the applicable integration. return; } @@ -104,7 +104,7 @@ public function config_page_scripts() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; - if ( in_array( $page, [ WPSEO_Admin::PAGE_IDENTIFIER, New_Dashboard_Page_Integration::PAGE, 'wpseo_workouts' ], true ) ) { + if ( in_array( $page, [ WPSEO_Admin::PAGE_IDENTIFIER, 'wpseo_workouts' ], true ) ) { wp_enqueue_media(); $script_data['userEditUrl'] = add_query_arg( 'user_id', '{user_id}', admin_url( 'user-edit.php' ) ); diff --git a/packages/js/src/dashboard/app.js b/packages/js/src/dashboard/app.js index efaf66a6a54..354fc9d15dd 100644 --- a/packages/js/src/dashboard/app.js +++ b/packages/js/src/dashboard/app.js @@ -8,7 +8,10 @@ import classNames from "classnames"; import PropTypes from "prop-types"; import { Link, Route, Routes, useLocation } from "react-router-dom"; import FirstTimeConfigurationSteps from "../first-time-configuration/first-time-configuration-steps"; +import { useSelectSettings } from "../settings/hooks"; import { MenuItemLink, YoastLogo } from "../shared-admin/components"; +import { PremiumUpsellList } from "../shared-admin/components/premium-upsell-list"; +import SidebarRecommendations from "./components/sidebar-recommendations"; import { useSelectDashboard } from "./hooks"; /** @@ -73,6 +76,9 @@ const Content = () => { const App = () => { const { pathname } = useLocation(); const isPremium = useSelectDashboard( "selectPreference", [], "isPremium" ); + const premiumLink = useSelectDashboard( "selectLink", [], "https://yoa.st/17h" ); + const premiumUpsellConfig = useSelectDashboard( "selectUpsellSettingsAsProps" ); + const promotions = useSelectDashboard( "selectPreference", [], "promotions", [] ); return ( { + { ! isPremium && }
+
diff --git a/packages/js/src/dashboard/components/sidebar-recommendations.js b/packages/js/src/dashboard/components/sidebar-recommendations.js new file mode 100644 index 00000000000..a99adf8e5bc --- /dev/null +++ b/packages/js/src/dashboard/components/sidebar-recommendations.js @@ -0,0 +1,25 @@ +import { AcademyUpsellCard, PremiumUpsellCard, RecommendationsSidebar } from "../../shared-admin/components"; +import { useSelectDashboard } from "../hooks"; +import { get } from "lodash"; +/** + * @returns {JSX.Element} The sidebar recommendations. + */ +const SidebarRecommendations = () => { + const isPremium = useSelectDashboard( "selectPreference", [], "isPremium" ); + const promotions = useSelectDashboard( "selectPreference", [], "promotions", [] ); + const premiumLink = useSelectDashboard( "selectLink", [], "https://yoa.st/jj" ); + const premiumUpsellConfig = useSelectDashboard( "selectUpsellSettingsAsProps" ); + const academyLink = useSelectDashboard( "selectLink", [], "https://yoa.st/3t6" ); + if ( isPremium ) { + return null; + } + + return ( + + + + + ); +}; + +export default SidebarRecommendations; diff --git a/packages/js/src/settings/app.js b/packages/js/src/settings/app.js index ae0bf9f81af..3badff87e76 100644 --- a/packages/js/src/settings/app.js +++ b/packages/js/src/settings/app.js @@ -1,17 +1,17 @@ /* eslint-disable react/jsx-max-depth */ import { Transition } from "@headlessui/react"; -import { AdjustmentsIcon, ArrowNarrowRightIcon, ColorSwatchIcon, DesktopComputerIcon, NewspaperIcon } from "@heroicons/react/outline"; +import { AdjustmentsIcon, ColorSwatchIcon, DesktopComputerIcon, NewspaperIcon } from "@heroicons/react/outline"; import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/solid"; -import { createInterpolateElement, useCallback, useMemo } from "@wordpress/element"; -import { __, sprintf } from "@wordpress/i18n"; -import { Badge, Button, ChildrenLimiter, ErrorBoundary, Paper, SidebarNavigation, Title, useBeforeUnload, useSvgAria } from "@yoast/ui-library"; +import { useCallback, useMemo } from "@wordpress/element"; +import { __ } from "@wordpress/i18n"; +import { Badge, ChildrenLimiter, ErrorBoundary, Paper, SidebarNavigation, useBeforeUnload, useSvgAria } from "@yoast/ui-library"; import classNames from "classnames"; import { useFormikContext } from "formik"; import { map } from "lodash"; import PropTypes from "prop-types"; import { Link, Navigate, Route, Routes, useLocation } from "react-router-dom"; -import { getPremiumBenefits } from "../helpers/get-premium-benefits"; import { MenuItemLink, YoastLogo } from "../shared-admin/components"; +import { PremiumUpsellList } from "../shared-admin/components/premium-upsell-list"; import { ErrorFallback, Notifications, Search, SidebarRecommendations } from "./components"; import { useRouterScrollRestore, useSelectSettings } from "./hooks"; import { @@ -158,58 +158,6 @@ Menu.propTypes = { taxonomies: PropTypes.object.isRequired, idSuffix: PropTypes.string, }; - -/** - * @returns {JSX.Element} The element. - */ -const PremiumUpsellList = () => { - const premiumLink = useSelectSettings( "selectLink", [], "https://yoa.st/17h" ); - const premiumUpsellConfig = useSelectSettings( "selectUpsellSettingsAsProps" ); - const promotions = useSelectSettings( "selectPreference", [], "promotions", [] ); - const isBlackFriday = promotions.includes( "black-friday-2024-promotion" ); - - return ( - - { isBlackFriday &&
-
{ __( "30% OFF | Code: BF2024", "wordpress-seo" ) }
-
} -
- - { sprintf( - /* translators: %s expands to "Yoast SEO" Premium */ - __( "Upgrade to %s", "wordpress-seo" ), - "Yoast SEO Premium" - ) } - -
    - { getPremiumBenefits().map( ( benefit, index ) => ( -
  • - { createInterpolateElement( benefit, { strong: } ) } -
  • - ) ) } -
- -
-
- ); -}; - /** * @returns {JSX.Element} The app component. */ @@ -218,7 +166,9 @@ const App = () => { const postTypes = useSelectSettings( "selectPostTypes" ); const taxonomies = useSelectSettings( "selectTaxonomies" ); const isPremium = useSelectSettings( "selectPreference", [], "isPremium" ); - + const premiumLink = useSelectSettings( "selectLink", [], "https://yoa.st/17h" ); + const premiumUpsellConfig = useSelectSettings( "selectUpsellSettingsAsProps" ); + const promotions = useSelectSettings( "selectPreference", [], "promotions", [] ); useRouterScrollRestore(); const { dirty } = useFormikContext(); @@ -297,7 +247,11 @@ const App = () => { - { ! isPremium && } + { ! isPremium && }
diff --git a/packages/js/src/shared-admin/components/premium-upsell-list.js b/packages/js/src/shared-admin/components/premium-upsell-list.js new file mode 100644 index 00000000000..4f3bfb24124 --- /dev/null +++ b/packages/js/src/shared-admin/components/premium-upsell-list.js @@ -0,0 +1,67 @@ +import { ArrowNarrowRightIcon } from "@heroicons/react/outline"; +import { createInterpolateElement, useMemo } from "@wordpress/element"; +import { __, sprintf } from "@wordpress/i18n"; +import { Button, Title, Paper } from "@yoast/ui-library"; +import PropTypes from "prop-types"; +import { getPremiumBenefits } from "../../helpers/get-premium-benefits"; + +/** + * @param {string} premiumLink The premium link. + * @param {Object} [premiumUpsellConfig] The premium upsell configuration data. + * @param {array} [promotions] Promotions. + * @returns {JSX.Element} The premium upsell card. + */ +export const PremiumUpsellList = ( { premiumLink, premiumUpsellConfig, promotions } ) => { + const isBlackFriday = promotions.includes( "black-friday-2024-promotion" ); + return ( + + { isBlackFriday &&
+
{ __( "30% OFF | Code: BF2024", "wordpress-seo" ) }
+
} +
+ + { sprintf( + /* translators: %s expands to "Yoast SEO" Premium */ + __( "Upgrade to %s", "wordpress-seo" ), + "Yoast SEO Premium" + ) } + +
    + { getPremiumBenefits().map( ( benefit, index ) => ( +
  • + { createInterpolateElement( benefit, { strong: } ) } +
  • + ) ) } +
+ +
+
+ ); +}; + +PremiumUpsellList.propTypes = { + premiumLink: PropTypes.string.isRequired, + premiumUpsellConfig: PropTypes.object, + promotions: PropTypes.array, +}; + +PremiumUpsellList.defaultProps = { + premiumUpsellConfig: {}, + promotions: [], +}; diff --git a/src/dashboard/user-interface/new-dashboard-page-integration.php b/src/dashboard/user-interface/new-dashboard-page-integration.php index adc05a50a06..a0527f2eefc 100644 --- a/src/dashboard/user-interface/new-dashboard-page-integration.php +++ b/src/dashboard/user-interface/new-dashboard-page-integration.php @@ -9,6 +9,8 @@ use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; +use function add_query_arg; +use function admin_url; /** * Class New_Dashboard_Page_Integration. @@ -143,7 +145,7 @@ public function enqueue_assets() { \wp_enqueue_media(); $this->asset_manager->enqueue_script( 'new-dashboard' ); $this->asset_manager->enqueue_style( 'new-dashboard' ); - $this->asset_manager->localize_script( 'dashboard', 'wpseoScriptData', $this->get_script_data() ); + $this->asset_manager->localize_script( 'new-dashboard', 'wpseoScriptData', $this->get_script_data() ); } /** @@ -163,6 +165,7 @@ private function get_script_data() { ], ], 'linkParams' => $this->shortlink_helper->get_query_params(), + 'userEditUrl' => add_query_arg( 'user_id', '{user_id}', admin_url( 'user-edit.php' ) ) ]; } } From 7ba6a6c7187302bd8ea923a94ba68dc7215eb0fc Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Wed, 18 Sep 2024 10:14:10 +0200 Subject: [PATCH 155/313] Add a missing argument --- packages/yoastseo/src/app.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/yoastseo/src/app.js b/packages/yoastseo/src/app.js index 99c54bdf2d4..2dcd0924c1f 100644 --- a/packages/yoastseo/src/app.js +++ b/packages/yoastseo/src/app.js @@ -216,6 +216,8 @@ class App { this.callbacks = this.config.callbacks; + this.researcher = this.config.researcher; + setLocaleData( this.config.translations.locale_data[ "wordpress-seo" ], "wordpress-seo" ); this.initializeAssessors( args ); @@ -315,8 +317,8 @@ class App { return; } - this.defaultSeoAssessor = new SEOAssessor( { marker: this.config.marker } ); - this.cornerStoneSeoAssessor = new CornerstoneSEOAssessor( { marker: this.config.marker } ); + this.defaultSeoAssessor = new SEOAssessor( this.researcher, { marker: this.config.marker } ); + this.cornerStoneSeoAssessor = new CornerstoneSEOAssessor( this.researcher, { marker: this.config.marker } ); // Set the assessor if ( isUndefined( args.seoAssessor ) ) { @@ -337,8 +339,10 @@ class App { return; } - this.defaultContentAssessor = new ContentAssessor( { marker: this.config.marker, locale: this.config.locale } ); - this.cornerStoneContentAssessor = new CornerstoneContentAssessor( { marker: this.config.marker, locale: this.config.locale } ); + this.defaultContentAssessor = new ContentAssessor( this.researcher, { marker: this.config.marker, locale: this.config.locale } ); + this.cornerStoneContentAssessor = new CornerstoneContentAssessor( this.researcher, + { marker: this.config.marker, locale: this.config.locale } + ); // Set the content assessor if ( isUndefined( args._contentAssessor ) ) { From 9e946f594014c73743c27d895db8651f91ef1b1b Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 18 Sep 2024 10:18:59 +0200 Subject: [PATCH 156/313] Moves upsells to alert-center component --- packages/js/src/dashboard/app.js | 12 ------ .../js/src/dashboard/routes/alert-center.js | 43 +++++++++++++------ 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/js/src/dashboard/app.js b/packages/js/src/dashboard/app.js index ff053eae582..638ada39390 100644 --- a/packages/js/src/dashboard/app.js +++ b/packages/js/src/dashboard/app.js @@ -7,8 +7,6 @@ import { SidebarNavigation, useSvgAria } from "@yoast/ui-library"; import PropTypes from "prop-types"; import { Link, Route, Routes, useLocation } from "react-router-dom"; import { MenuItemLink, YoastLogo } from "../shared-admin/components"; -import { PremiumUpsellList } from "../shared-admin/components/premium-upsell-list"; -import SidebarRecommendations from "./components/sidebar-recommendations"; import { useSelectDashboard } from "./hooks"; import { FirstTimeConfiguration, AlertCenter } from "./routes"; @@ -52,10 +50,6 @@ Menu.propTypes = { */ const App = () => { const { pathname } = useLocation(); - const isPremium = useSelectDashboard( "selectPreference", [], "isPremium" ); - const premiumLink = useSelectDashboard( "selectLink", [], "https://yoa.st/17h" ); - const premiumUpsellConfig = useSelectDashboard( "selectUpsellSettingsAsProps" ); - const promotions = useSelectDashboard( "selectPreference", [], "promotions", [] ); return ( { - { ! isPremium && }
-
diff --git a/packages/js/src/dashboard/routes/alert-center.js b/packages/js/src/dashboard/routes/alert-center.js index bd2ca116ada..48fb869f7a5 100644 --- a/packages/js/src/dashboard/routes/alert-center.js +++ b/packages/js/src/dashboard/routes/alert-center.js @@ -1,25 +1,40 @@ import { __ } from "@wordpress/i18n"; import { Paper, Title } from "@yoast/ui-library"; +import { PremiumUpsellList } from "../../shared-admin/components/premium-upsell-list"; import { Notifications, Problems } from "../components"; - +import SidebarRecommendations from "../components/sidebar-recommendations"; +import { useSelectDashboard } from "../hooks"; +import classNames from "classnames"; /** * @returns {JSX.Element} The dashboard content placeholder. */ export const AlertCenter = () => { + const isPremium = useSelectDashboard( "selectPreference", [], "isPremium" ); + const premiumLink = useSelectDashboard( "selectLink", [], "https://yoa.st/17h" ); + const premiumUpsellConfig = useSelectDashboard( "selectUpsellSettingsAsProps" ); + const promotions = useSelectDashboard( "selectPreference", [], "promotions", [] ); return <> - -
-
- { __( "Alert center", "wordpress-seo" ) } -

- { __( "Monitor and manage potential SEO problems affecting your site and stay informed with important notifications and updates.", "wordpress-seo" ) } -

-
-
-
-
- - +
+ +
+
+ { __( "Alert center", "wordpress-seo" ) } +

+ { __( "Monitor and manage potential SEO problems affecting your site and stay informed with important notifications and updates.", "wordpress-seo" ) } +

+
+
+
+
+ + + { ! isPremium && } +
+
; }; From 6bfde827b64fa17a1d9b0c0f03f13bab5c9e66fe Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Wed, 18 Sep 2024 10:24:49 +0200 Subject: [PATCH 157/313] Improve JSDocs --- packages/yoastseo/src/app.js | 118 +++++++++--------- .../src/scoring/assessors/contentAssessor.js | 2 +- 2 files changed, 57 insertions(+), 63 deletions(-) diff --git a/packages/yoastseo/src/app.js b/packages/yoastseo/src/app.js index 2dcd0924c1f..81aedbbf1d5 100644 --- a/packages/yoastseo/src/app.js +++ b/packages/yoastseo/src/app.js @@ -79,7 +79,7 @@ const defaults = { * Check arguments passed to the App to check if all necessary arguments are set. * * @private - * @param {Object} args The arguments object passed to the App. + * @param {Object} args The arguments object passed to the App. * @returns {void} */ function verifyArguments( args ) { @@ -93,15 +93,15 @@ function verifyArguments( args ) { } /** - * This should return an object with the given properties + * This should return an object with the given properties. * * @callback YoastSEO.App~getData - * @returns {Object} data - * @returns {String} data.keyword The keyword that should be used - * @returns {String} data.meta - * @returns {String} data.text The text to analyze - * @returns {String} data.metaTitle The text in the HTML title tag - * @returns {String} data.title The title to analyze + * @returns {Object} data. The data object containing the following properties: keyword, meta, text, metaTitle, title, url, excerpt. + * @returns {String} data.keyword The keyword that should be used. + * @returns {String} data.meta The meta description to analyze. + * @returns {String} data.text The text to analyze. + * @returns {String} data.metaTitle The text in the HTML title tag. + * @returns {String} data.title The title to analyze. * @returns {String} data.url The URL for the given page * @returns {String} data.excerpt Excerpt for the pages */ @@ -109,7 +109,7 @@ function verifyArguments( args ) { /** * @callback YoastSEO.App~getAnalyzerInput * - * @returns {Array} An array containing the analyzer queue + * @returns {Array} An array containing the analyzer queue. */ /** @@ -121,7 +121,7 @@ function verifyArguments( args ) { /** * @callback YoastSEO.App~updateSnippetValues * - * @param {Object} ev The event emitted from the DOM + * @param {Object} ev The event emitted from the DOM. */ /** @@ -159,44 +159,38 @@ function verifyArguments( args ) { */ /** - * Loader for the analyzer, loads the eventbinder and the elementdefiner - * - * @param {Object} args The arguments passed to the loader. - * @param {Object} args.translations Jed compatible translations. - * @param {Object} args.targets Targets to retrieve or set on. - * @param {String} args.targets.snippet ID for the snippet preview element. - * @param {String} args.targets.output ID for the element to put the output of the analyzer in. - * @param {int} args.typeDelay Number of milliseconds to wait between typing to refresh the analyzer output. - * @param {boolean} args.dynamicDelay Whether to enable dynamic delay, will ignore type delay if the analyzer takes a long time. - * Applicable on slow devices. - * @param {int} args.maxTypeDelay The maximum amount of type delay even if dynamic delay is on. - * @param {int} args.typeDelayStep The amount with which to increase the typeDelay on each step when dynamic delay is enabled. - * @param {Object} args.callbacks The callbacks that the app requires. - * @param {Object} args.assessor The Assessor to use instead of the default assessor. - * @param {YoastSEO.App~getData} args.callbacks.getData Called to retrieve input data - * @param {YoastSEO.App~getAnalyzerInput} args.callbacks.getAnalyzerInput Called to retrieve input for the analyzer. - * @param {YoastSEO.App~bindElementEvents} args.callbacks.bindElementEvents Called to bind events to the DOM elements. - * @param {YoastSEO.App~updateSnippetValues} args.callbacks.updateSnippetValues Called when the snippet values need to be updated. - * @param {YoastSEO.App~saveScores} args.callbacks.saveScores Called when the score has been determined by the analyzer. - * @param {YoastSEO.App~saveContentScore} args.callback.saveContentScore Called when the content score has been - * determined by the assessor. - * @param {YoastSEO.App~updatedContentResults} args.callbacks.updatedContentResults Called when the score has been determined - * by the analyzer. - * @param {YoastSEO.App~updatedKeywordsResults} args.callback.updatedKeywordsResults Called when the content score has been - * determined by the assessor. - * @param {Function} args.callbacks.saveSnippetData Function called when the snippet data is changed. - * @param {Function} args.marker The marker to use to apply the list of marks retrieved from an assessment. - * - * @param {boolean} [args.debouncedRefresh] Whether or not to debounce the - * refresh function. Defaults to true. - * @param {Researcher} args.researcher The Researcher object to be used. - * - * @constructor + * Represents the main YoastSEO App. */ class App { /** - * Creates a new App instance. - * @param {Object} args The arguments. + * Loader for the analyzer, loads the eventbinder and the elementdefiner + * + * @param {Object} args The arguments passed to the loader. + * @param {Object} args.translations Jed compatible translations. + * @param {Object} args.targets Targets to retrieve or set on. + * @param {String} args.targets.snippet ID for the snippet preview element. + * @param {String} args.targets.output ID for the element to put the output of the analyzer in. + * @param {int} args.typeDelay Number of milliseconds to wait between typing to refresh the analyzer output. + * @param {boolean} args.dynamicDelay Whether to enable dynamic delay, will ignore type delay if the analyzer takes a long time. Applicable on slow devices. + * @param {int} args.maxTypeDelay The maximum amount of type delay even if dynamic delay is on. + * @param {int} args.typeDelayStep The amount with which to increase the typeDelay on each step when dynamic delay is enabled. + * @param {Object} args.callbacks The callbacks that the app requires. + * @param {Object} args.assessor The Assessor to use instead of the default assessor. + * @param {YoastSEO.App~getData} args.callbacks.getData Called to retrieve input data + * @param {YoastSEO.App~getAnalyzerInput} args.callbacks.getAnalyzerInput Called to retrieve input for the analyzer. + * @param {YoastSEO.App~bindElementEvents} args.callbacks.bindElementEvents Called to bind events to the DOM elements. + * @param {YoastSEO.App~updateSnippetValues} args.callbacks.updateSnippetValues Called when the snippet values need to be updated. + * @param {YoastSEO.App~saveScores} args.callbacks.saveScores Called when the score has been determined by the analyzer. + * @param {YoastSEO.App~saveContentScore} args.callback.saveContentScore Called when the content score has been determined by the assessor. + * @param {YoastSEO.App~updatedContentResults} args.callbacks.updatedContentResults Called when the score has been determined by the analyzer. + * @param {YoastSEO.App~updatedKeywordsResults} args.callback.updatedKeywordsResults Called when the content score has been determined by the assessor. + * @param {Function} args.callbacks.saveSnippetData Function called when the snippet data is changed. + * @param {Function} args.marker The marker to use to apply the list of marks retrieved from an assessment. + * + * @param {boolean} [args.debouncedRefresh=true] Whether or not to debounce the refresh function. Defaults to true. + * @param {Researcher} args.researcher The Researcher object to be used. + * + * @constructor */ constructor( args ) { if ( ! isObject( args ) ) { @@ -355,8 +349,9 @@ class App { /** * Extends the config with defaults. * - * @param {Object} args The arguments to be extended. - * @returns {Object} args The extended arguments. + * @param {Object} args The arguments to be extended. + * + * @returns {Object} The extended arguments. */ extendConfig( args ) { args.sampleText = this.extendSampleText( args.sampleText ); @@ -368,8 +363,8 @@ class App { /** * Extends sample text config with defaults. * - * @param {Object} sampleText The sample text to be extended. - * @returns {Object} sampleText The extended sample text. + * @param {Object} sampleText The sample text to be extended. + * @returns {Object} The extended sample text. */ extendSampleText( sampleText ) { const defaultSampleText = defaults.sampleText; @@ -458,12 +453,12 @@ class App { } /** - * Initializes the assessor presenters for content and SEO. + * Initializes the assessor presenters for content and SEO analysis. * * @returns {void} */ initAssessorPresenters() { - // Pass the assessor result through to the formatter + // Pass the assessor result through to the formatter. if ( ! isUndefined( this.config.targets.output ) ) { this.seoAssessorPresenter = new AssessorPresenter( { targets: { @@ -474,7 +469,7 @@ class App { } if ( ! isUndefined( this.config.targets.contentOutput ) ) { - // Pass the assessor result through to the formatter + // Pass the assessor result through to the formatter. this.contentAssessorPresenter = new AssessorPresenter( { targets: { output: this.config.targets.contentOutput, @@ -508,8 +503,7 @@ class App { } /** - * Inits a new pageAnalyzer with the inputs from the getInput function and calls the scoreFormatter - * to format outputs. + * Inits a new pageAnalyzer with the inputs from the getInput function and calls the scoreFormatter to format outputs. * * @returns {void} */ @@ -527,12 +521,12 @@ class App { let text = this.analyzerData.text; - // Insert HTML stripping code + // Insert HTML stripping code. text = removeHtmlBlocks( text ); const titleWidth = this.analyzerData.titleWidth; - // Create a paper object for the Researcher + // Create a paper object for the Researcher. this.paper = new Paper( text, { keyword: this.analyzerData.keyword, synonyms: this.analyzerData.synonyms, @@ -664,7 +658,7 @@ class App { } /** - * Removes the pluging load dialog. + * Removes the plugin load dialog. * * @returns {void} */ @@ -713,8 +707,8 @@ class App { /** * Delegates to `YoastSEO.app.pluggable.registerModification`. * - * @param {string} modification The name of the filter - * @param {function} callable The callable function + * @param {string} modification The name of the filter. + * @param {function} callable The callable function. * @param {string} pluginName The plugin that is registering the modification. * @param {number} [priority] Used to specify the order in which the callables associated with a particular filter are called. * Lower numbers correspond with earlier execution. @@ -727,7 +721,7 @@ class App { /** * Registers a custom assessment for use in the analyzer, this will result in a new line in the analyzer results. - * The function needs to use the assessmentresult to return an result based on the contents of the page/posts. + * The function needs to use the assessment result to return a result based on the contents of the page/posts. * * Score 0 results in a grey circle if it is not explicitly set by using setscore * Scores 0, 1, 2, 3 and 4 result in a red circle @@ -735,7 +729,7 @@ class App { * Scores 8, 9 and 10 result in a green circle * * @param {string} name Name of the test. - * @param {function} assessment The assessment to run + * @param {function} assessment The assessment to run. * @param {string} pluginName The plugin that is registering the test. * @returns {boolean} Whether or not the test was successfully registered. */ @@ -798,7 +792,7 @@ class App { * to a SEO score. * * Negative scores result in a red circle - * Scores 1, 2, 3, 4 and 5 result in a orange circle + * Scores 1, 2, 3, 4 and 5 result in an orange circle * Scores 6 and 7 result in a yellow circle * Scores 8, 9 and 10 result in a red circle * diff --git a/packages/yoastseo/src/scoring/assessors/contentAssessor.js b/packages/yoastseo/src/scoring/assessors/contentAssessor.js index 84fe27c1f5d..e42802f79b2 100644 --- a/packages/yoastseo/src/scoring/assessors/contentAssessor.js +++ b/packages/yoastseo/src/scoring/assessors/contentAssessor.js @@ -70,7 +70,7 @@ export default class ContentAssessor extends Assessor { } /** - * Determines whether a language is fully supported. If a language supports 8 content assessments + * Determines whether a language is fully supported. If a language supports 7 content assessments, * it is fully supported * * @returns {boolean} True if fully supported. From 0ac5198b75409b8c53dab0d6aae63866483b6d1b Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Wed, 18 Sep 2024 10:25:25 +0200 Subject: [PATCH 158/313] Fix incorrect deprecation version --- .../scoring/assessments/seo/UrlKeywordAssessment.js | 10 +++++----- packages/yoastseo/src/values/Paper.js | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js b/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js index 890a076e622..ec22949171c 100644 --- a/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js +++ b/packages/yoastseo/src/scoring/assessments/seo/UrlKeywordAssessment.js @@ -78,7 +78,7 @@ class SlugKeywordAssessment extends Assessment { return { score: this._config.scores.good, resultText: sprintf( - /* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */ + /* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */ __( "%1$sKeyphrase in slug%2$s: Great work!", "wordpress-seo" @@ -92,7 +92,7 @@ class SlugKeywordAssessment extends Assessment { return { score: this._config.scores.okay, resultText: sprintf( - /* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */ + /* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */ __( "%1$sKeyphrase in slug%3$s: (Part of) your keyphrase does not appear in the slug. %2$sChange that%3$s!", "wordpress-seo" @@ -108,7 +108,7 @@ class SlugKeywordAssessment extends Assessment { return { score: this._config.scores.good, resultText: sprintf( - /* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */ + /* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag */ __( "%1$sKeyphrase in slug%2$s: More than half of your keyphrase appears in the slug. That's great!", "wordpress-seo" @@ -121,7 +121,7 @@ class SlugKeywordAssessment extends Assessment { return { score: this._config.scores.okay, resultText: sprintf( - /* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */ + /* translators: %1$s and %2$s expand to links on yoast.com, %3$s expands to the anchor end tag */ __( "%1$sKeyphrase in slug%3$s: (Part of) your keyphrase does not appear in the slug. %2$sChange that%3$s!", "wordpress-seo" @@ -139,7 +139,7 @@ class SlugKeywordAssessment extends Assessment { * UrlKeywordAssessment was the previous name for SlugKeywordAssessment (hence the name of this file). * We keep (and expose) this assessment for backwards compatibility. * - * @deprecated Since version 18.8 Use SlugKeywordAssessment instead. + * @deprecated Since version 1.19.1. Use SlugKeywordAssessment instead. */ class UrlKeywordAssessment extends SlugKeywordAssessment { /** diff --git a/packages/yoastseo/src/values/Paper.js b/packages/yoastseo/src/values/Paper.js index e6b1e5a79fc..abdd9a777f5 100644 --- a/packages/yoastseo/src/values/Paper.js +++ b/packages/yoastseo/src/values/Paper.js @@ -57,7 +57,7 @@ class Paper { } if ( attributes.hasOwnProperty( "url" ) ) { - // The 'url' attribute has been deprecated since version 18.8, refer to hasUrl and getUrl below. + // The 'url' attribute has been deprecated since version 1.19.1, refer to hasUrl and getUrl below. console.warn( "The 'url' attribute is deprecated, use 'slug' instead." ); attributes.slug = attributes.url || attributes.slug; } @@ -206,7 +206,7 @@ class Paper { /** * Checks whether an url is available - * @deprecated Since version 18.7. Use hasSlug instead. + * @deprecated Since version 1.19.1. Use hasSlug instead. * @returns {boolean} Returns true if the Paper has a slug. */ hasUrl() { @@ -216,7 +216,7 @@ class Paper { /** * Returns the url, or an empty string if no url is available. - * @deprecated Since version 18.8. Use getSlug instead. + * @deprecated Since version 1.19.1. Use getSlug instead. * @returns {string} Returns the url */ getUrl() { From 81d0913da2effc0b5823de7293bd467331d5c987 Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Wed, 18 Sep 2024 10:31:09 +0200 Subject: [PATCH 159/313] Improve JSDoc --- packages/yoastseo/src/helpers/factory.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/yoastseo/src/helpers/factory.js b/packages/yoastseo/src/helpers/factory.js index 97e7b8ea1ae..7ec193aa082 100644 --- a/packages/yoastseo/src/helpers/factory.js +++ b/packages/yoastseo/src/helpers/factory.js @@ -23,10 +23,10 @@ export default class FactoryProto { * @param {Object} expectedValue Expected value. * @param {boolean} [multiValue=false] Whether the researcher has multiple values. * @param {boolean} [hasMorphologyData=false] Whether the researcher has morphology data. - * @param {Object} [config=false] Optional config to be used for an assessment. - * @param {Object} [helpers=false] Optional helpers to be used for an assessment. + * @param {Object|boolean} [config=false] Optional config to be used for an assessment. + * @param {Object|boolean} [helpers=false] Optional helpers to be used for an assessment. * - * @returns {Researcher} Mock researcher. + * @returns {Object} Mock researcher. */ static buildMockResearcher( expectedValue, multiValue = false, hasMorphologyData = false, config = false, helpers = false ) { @@ -167,12 +167,13 @@ export default class FactoryProto { }, /** - * Returns whether the worker has the specified helper. + * Checks whether a helper with the given name exists. + * @param {string} name The name to check. * - * @returns {boolean} Whether the worker has the helper. + * @returns {boolean} True if the helper exists. */ - hasHelper: function() { - return expectedValue; + hasHelper: function( name ) { + return ! isUndefined( helpers[ name ] ); }, /** @@ -186,11 +187,11 @@ export default class FactoryProto { /** * Returns whether the worker has the specified config. - * @param {string} research The name of the config. + * @param {string} name The name of the config. * @returns {boolean} Whether the worker has the specified config. */ - hasConfig: function( config ) { - return ! isUndefined( expectedValue[ config ] ); + hasConfig: function( name ) { + return ! isUndefined( expectedValue[ name ] ); }, }; } From d47900fc9b43457703fde7a5368934399f21bec3 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 18 Sep 2024 10:34:36 +0200 Subject: [PATCH 160/313] Change imports to use global namespace --- .../user-interface/new-dashboard-page-integration.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/dashboard/user-interface/new-dashboard-page-integration.php b/src/dashboard/user-interface/new-dashboard-page-integration.php index a0527f2eefc..010be7e53ac 100644 --- a/src/dashboard/user-interface/new-dashboard-page-integration.php +++ b/src/dashboard/user-interface/new-dashboard-page-integration.php @@ -9,8 +9,6 @@ use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; -use function add_query_arg; -use function admin_url; /** * Class New_Dashboard_Page_Integration. @@ -165,7 +163,7 @@ private function get_script_data() { ], ], 'linkParams' => $this->shortlink_helper->get_query_params(), - 'userEditUrl' => add_query_arg( 'user_id', '{user_id}', admin_url( 'user-edit.php' ) ) + 'userEditUrl' => \add_query_arg( 'user_id', '{user_id}', \admin_url( 'user-edit.php' ) ) ]; } } From f8bdf1457cd7b48ee9050cb027ccd08fbbf540d6 Mon Sep 17 00:00:00 2001 From: aidamarfuaty Date: Wed, 18 Sep 2024 10:48:50 +0200 Subject: [PATCH 161/313] Adapt unit tests --- packages/yoastseo/spec/appSpec.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/yoastseo/spec/appSpec.js b/packages/yoastseo/spec/appSpec.js index 1e437ddc451..8aa5cc91d7c 100644 --- a/packages/yoastseo/spec/appSpec.js +++ b/packages/yoastseo/spec/appSpec.js @@ -1,5 +1,6 @@ import MissingArgument from "../src/errors/missingArgument.js"; import App from "../src/app.js"; +import Factory from "../src/helpers/factory"; // Mock these function to prevent us from needing an actual DOM in the tests. App.prototype.showLoadingDialog = function() {}; @@ -16,6 +17,8 @@ document.getElementById = function() { return mockElement; }; +const researcher = Factory.buildMockResearcher( {}, true, false ); + describe( "Creating an App", function() { it( "throws an error when no args are given", function() { expect( () => new App() ).toThrowError( MissingArgument ); @@ -48,6 +51,17 @@ describe( "Creating an App", function() { } ).toThrowError( MissingArgument ); } ); + it( "throws on a missing researcher argument", function() { + expect( function() { + new App( { + targets: { + snippet: "snippetID", + output: "outputID", + }, + } ); + } ).toThrowError( MissingArgument ); + } ); + it( "should work without an output ID", function() { new App( { targets: { @@ -58,6 +72,7 @@ describe( "Creating an App", function() { return {}; }, }, + researcher: researcher, } ); } ); @@ -72,6 +87,7 @@ describe( "Creating an App", function() { return {}; }, }, + researcher: researcher, } ); } ); } ); From 52853bcf284d38d4d87e217aa055e674e7f0d197 Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Wed, 18 Sep 2024 11:49:21 +0300 Subject: [PATCH 162/313] Create notice component and add it in the new dashboard --- css/src/new-dashboard.css | 18 +++++++++ packages/js/src/dashboard/app.js | 6 +++ .../dashboard/tailwind-components/notice.js | 39 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 packages/js/src/dashboard/tailwind-components/notice.js diff --git a/css/src/new-dashboard.css b/css/src/new-dashboard.css index 1a7492bf206..9cdcacc140b 100644 --- a/css/src/new-dashboard.css +++ b/css/src/new-dashboard.css @@ -3,6 +3,24 @@ body.toplevel_page_wpseo_dashboard { display:none; } + .yoast-new-dashboard-notice { + background: white; + border: 1px solid #E0B3CC; + box-shadow: 0px 4px 6px -1px #0000001A; + } + + .yoast-new-dashboard-notice .yoast-icon { + background-color: var(--yoast-color-primary); + mask-image: var(--yoast-svg-icon-yoast); + -webkit-mask-image: var(--yoast-svg-icon-yoast); + mask-size: 100% 100%; + -webkit-mask-size: 100% 100%; + display: inline-block; + width: 14px; + height: 14px; + margin-right: 8px; + } + @apply yst-bg-slate-100; /* Move WP footer behind our content. */ diff --git a/packages/js/src/dashboard/app.js b/packages/js/src/dashboard/app.js index 22d9da54941..e45e4a01ac7 100644 --- a/packages/js/src/dashboard/app.js +++ b/packages/js/src/dashboard/app.js @@ -10,6 +10,7 @@ import { Link, Route, Routes, useLocation } from "react-router-dom"; import { MenuItemLink, YoastLogo } from "../shared-admin/components"; import { useSelectDashboard } from "./hooks"; import { FirstTimeConfiguration } from "./routes"; +import Notice from "./tailwind-components/notice"; /** * @param {string} [idSuffix] Extra id suffix. Can prevent double IDs on the page. @@ -94,6 +95,11 @@ const App = () => {
+ + content + +
+ +

{ title }

+ +
+

+ { children } +

+
+ ); +} + +Notice.propTypes = { + title: PropTypes.string.isRequired, + children: PropTypes.string.isRequired, +}; \ No newline at end of file From 871fb27860e9d5b4bdf88c968730d22bd6f33388 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Wed, 18 Sep 2024 11:54:03 +0200 Subject: [PATCH 163/313] Cs. --- admin/class-config.php | 2 +- src/dashboard/user-interface/new-dashboard-page-integration.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/class-config.php b/admin/class-config.php index c47419ccebe..4346107573b 100644 --- a/admin/class-config.php +++ b/admin/class-config.php @@ -50,7 +50,7 @@ public function __construct() { public function init() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; - if ( in_array( $page, [ Settings_Integration::PAGE, Academy_Integration::PAGE, Support_Integration::PAGE, New_Dashboard_Page_Integration::PAGE,], true ) ) { + if ( in_array( $page, [ Settings_Integration::PAGE, Academy_Integration::PAGE, Support_Integration::PAGE, New_Dashboard_Page_Integration::PAGE ], true ) ) { // Bail, this is managed in the applicable integration. return; } diff --git a/src/dashboard/user-interface/new-dashboard-page-integration.php b/src/dashboard/user-interface/new-dashboard-page-integration.php index 010be7e53ac..144fa14cddd 100644 --- a/src/dashboard/user-interface/new-dashboard-page-integration.php +++ b/src/dashboard/user-interface/new-dashboard-page-integration.php @@ -163,7 +163,7 @@ private function get_script_data() { ], ], 'linkParams' => $this->shortlink_helper->get_query_params(), - 'userEditUrl' => \add_query_arg( 'user_id', '{user_id}', \admin_url( 'user-edit.php' ) ) + 'userEditUrl' => \add_query_arg( 'user_id', '{user_id}', \admin_url( 'user-edit.php' ) ), ]; } } From c0875ab27bdcbdc480e44cb7c03ed76e24e9df85 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Wed, 18 Sep 2024 14:11:34 +0300 Subject: [PATCH 164/313] feat: add notifications and problems cards (u only) to new dashboard --- .../js/src/dashboard/components/box-title.js | 37 ++++++++++++++ packages/js/src/dashboard/components/index.js | 2 + packages/js/src/dashboard/components/list.js | 51 +++++++++++++++++++ .../src/dashboard/components/notifications.js | 17 ++++++- .../js/src/dashboard/components/problems.js | 19 ++++++- 5 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 packages/js/src/dashboard/components/box-title.js create mode 100644 packages/js/src/dashboard/components/list.js diff --git a/packages/js/src/dashboard/components/box-title.js b/packages/js/src/dashboard/components/box-title.js new file mode 100644 index 00000000000..894e7f63a55 --- /dev/null +++ b/packages/js/src/dashboard/components/box-title.js @@ -0,0 +1,37 @@ +import { Title } from "@yoast/ui-library"; +import PropTypes from "prop-types"; +import classNames from "classnames"; + +/** + * + * @param {string} title The title of the card. + * @param {number} counts The count of the card. + * @param {string} Icon The icon of the card. + * @param {string} iconColor The color of the icon. + * + * @returns {JSX.Element} The card title component. + */ +export const BoxTitle = ( { + title = "", + counts = 0, + Icon = "", + iconColor = "red", +} ) => { + const colors = { + red: "yst-text-red-500", + blue: "yst-text-blue-500", + }; + return ( +
+ + { title } { counts > 0 && `(${counts})` } +
+ ); +}; + +BoxTitle.propTypes = { + title: PropTypes.string, + counts: PropTypes.number, + Icon: PropTypes.elementType, + iconColor: PropTypes.string, +}; diff --git a/packages/js/src/dashboard/components/index.js b/packages/js/src/dashboard/components/index.js index 2ff77285d50..e5264183e49 100644 --- a/packages/js/src/dashboard/components/index.js +++ b/packages/js/src/dashboard/components/index.js @@ -1,3 +1,5 @@ export { default as RouteLayout } from "./route-layout"; export { Notifications } from "./notifications"; export { Problems } from "./problems"; +export { List } from "./list"; +export { BoxTitle } from "./box-title"; diff --git a/packages/js/src/dashboard/components/list.js b/packages/js/src/dashboard/components/list.js new file mode 100644 index 00000000000..de036f28f83 --- /dev/null +++ b/packages/js/src/dashboard/components/list.js @@ -0,0 +1,51 @@ +import PropTypes from "prop-types"; +import { Button } from "@yoast/ui-library"; +import { EyeOffIcon, EyeIcon } from "@heroicons/react/outline"; +import classNames from "classnames"; + +/** + * + * @param {string} bulletColor The color of the bullet. + * @param {Array} items The list of items. + * @param {boolean} hidden Whether the items are hidden or not. + * + * @returns {JSX.Element} The list component. + */ +export const List = ( { bulletColor = "red", items = [], hidden = false } ) => { + const colors = { + red: "yst-fill-red-500", + blue: "yst-fill-blue-500", + }; + const Eye = hidden ? EyeIcon : EyeOffIcon; + return ( +
    + { items.map( ( item, index ) => ( +
  • +
    + + + +
    +
  • + ) ) } +
+ ); +}; + +List.propTypes = { + bulletColor: PropTypes.string, + items: PropTypes.arrayOf( PropTypes.shape( { + message: PropTypes.string, + } ) ), + hidden: PropTypes.bool, +}; diff --git a/packages/js/src/dashboard/components/notifications.js b/packages/js/src/dashboard/components/notifications.js index 9bb3cc9006d..7eff52d74d8 100644 --- a/packages/js/src/dashboard/components/notifications.js +++ b/packages/js/src/dashboard/components/notifications.js @@ -1,12 +1,25 @@ -import { Paper, Title } from "@yoast/ui-library"; +import { __ } from "@wordpress/i18n"; +import { BellIcon } from "@heroicons/react/outline"; +import { Paper } from "@yoast/ui-library"; +import { List } from "./list"; +import { BoxTitle } from "./box-title"; /** * @returns {JSX.Element} The notifications component. */ export const Notifications = () => { + const notificationsList = [ + { + message: __( "Your site is not connected to your MyYoast account. Connect your site to get access to all the features.", "wordpress-seo" ), + }, + { + message: __( "You have a new notification from Yoast SEO. Click here to read it.", "wordpress-seo" ), + }, + ]; return ( - Notifications + + ); }; diff --git a/packages/js/src/dashboard/components/problems.js b/packages/js/src/dashboard/components/problems.js index 445e8594b8a..97cf1b405c2 100644 --- a/packages/js/src/dashboard/components/problems.js +++ b/packages/js/src/dashboard/components/problems.js @@ -1,12 +1,27 @@ -import { Paper, Title } from "@yoast/ui-library"; +import { __ } from "@wordpress/i18n"; +import { ExclamationCircleIcon } from "@heroicons/react/outline"; +import { Paper } from "@yoast/ui-library"; +import { List } from "./list"; +import { BoxTitle } from "./box-title"; /** * @returns {JSX.Element} The problems component. */ export const Problems = () => { + const problemsList = [ + { + message: __( "Huge SEO issue: You're blocking access to robots. If you want search engines to show this site in their results, you must go to your Reading Settings and uncheck the box for Search Engine Visibility. I don't want this site to show in the search results.", "wordpress-seo" ), + }, + { + message: __( "You still have the default WordPress tagline, even an empty one is probably better. You can fix this in the customizer.", "wordpress-seo" ), + }, + ]; + return ( - Problems + +

{ __( "We have detected the following issues that affect the SEO of your site.", "wordpress-seo" ) }

+
); }; From 40fbd5e8242ef28890ad5adf2eaa3835beb92cf4 Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Wed, 18 Sep 2024 15:58:45 +0300 Subject: [PATCH 165/313] Move notices inside the new dashboard app --- css/src/new-dashboard.css | 31 +++++++++------ packages/js/src/dashboard/app.js | 20 +++++++--- .../dashboard/tailwind-components/notice.js | 39 ------------------- 3 files changed, 34 insertions(+), 56 deletions(-) delete mode 100644 packages/js/src/dashboard/tailwind-components/notice.js diff --git a/css/src/new-dashboard.css b/css/src/new-dashboard.css index 9cdcacc140b..49bdd256f3c 100644 --- a/css/src/new-dashboard.css +++ b/css/src/new-dashboard.css @@ -3,22 +3,31 @@ body.toplevel_page_wpseo_dashboard { display:none; } - .yoast-new-dashboard-notice { + #yoast-seo-dashboard .notice-yoast { background: white; border: 1px solid #E0B3CC; box-shadow: 0px 4px 6px -1px #0000001A; + border-radius: 0.375rem; + margin: 0px; + padding: 5px 12px; } - .yoast-new-dashboard-notice .yoast-icon { - background-color: var(--yoast-color-primary); - mask-image: var(--yoast-svg-icon-yoast); - -webkit-mask-image: var(--yoast-svg-icon-yoast); - mask-size: 100% 100%; - -webkit-mask-size: 100% 100%; - display: inline-block; - width: 14px; - height: 14px; - margin-right: 8px; + #yoast-seo-dashboard .notice-yoast p{ + padding-left: 22px; + } + + #yoast-seo-dashboard .notice-yoast img{ + display: none; + } + + #yoast-seo-dashboard .notice-yoast .notice-yoast__header{ + margin-bottom: 5px; + } + + #yoast-seo-dashboard .notice-yoast .notice-yoast__header h2{ + font-size: 13px; + font-weight: 500; + color: #1E293B; } @apply yst-bg-slate-100; diff --git a/packages/js/src/dashboard/app.js b/packages/js/src/dashboard/app.js index e45e4a01ac7..0a8ec98730f 100644 --- a/packages/js/src/dashboard/app.js +++ b/packages/js/src/dashboard/app.js @@ -3,6 +3,7 @@ import { Transition } from "@headlessui/react"; import { AdjustmentsIcon, BellIcon } from "@heroicons/react/outline"; import { __ } from "@wordpress/i18n"; +import { useEffect, useState } from "@wordpress/element"; import { Paper, SidebarNavigation, Title, useSvgAria } from "@yoast/ui-library"; import classNames from "classnames"; import PropTypes from "prop-types"; @@ -10,7 +11,6 @@ import { Link, Route, Routes, useLocation } from "react-router-dom"; import { MenuItemLink, YoastLogo } from "../shared-admin/components"; import { useSelectDashboard } from "./hooks"; import { FirstTimeConfiguration } from "./routes"; -import Notice from "./tailwind-components/notice"; /** * @param {string} [idSuffix] Extra id suffix. Can prevent double IDs on the page. @@ -74,6 +74,14 @@ const Content = () => { const App = () => { const { pathname } = useLocation(); const isPremium = useSelectDashboard( "selectPreference", [], "isPremium" ); + + const [ notices, setNotices ] = useState( [] ); + + useEffect( () => { + const allNotices = Array.from( document.querySelectorAll( ".notice-yoast" ) ); + setNotices( allNotices ); + }, [] ); + return ( {
- - content - + { + notices.map( ( notice, index ) => ( +
+ ) ) + } -
- -

{ title }

- -
-

- { children } -

-
- ); -} - -Notice.propTypes = { - title: PropTypes.string.isRequired, - children: PropTypes.string.isRequired, -}; \ No newline at end of file From 599cf2ac2e92844d264fb79b21e8936b7f2bd2d1 Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Wed, 18 Sep 2024 16:14:20 +0300 Subject: [PATCH 166/313] Remove the original notices --- css/src/new-dashboard.css | 6 +++++- packages/js/src/dashboard/app.js | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/css/src/new-dashboard.css b/css/src/new-dashboard.css index 49bdd256f3c..db97503ff0d 100644 --- a/css/src/new-dashboard.css +++ b/css/src/new-dashboard.css @@ -1,8 +1,12 @@ body.toplevel_page_wpseo_dashboard { - .notice:not(.notice-yoast) { + .notice { display:none; } + .yst-new-dashboard-notice .notice { + display:block; + } + #yoast-seo-dashboard .notice-yoast { background: white; border: 1px solid #E0B3CC; diff --git a/packages/js/src/dashboard/app.js b/packages/js/src/dashboard/app.js index 0a8ec98730f..56bddbaa56e 100644 --- a/packages/js/src/dashboard/app.js +++ b/packages/js/src/dashboard/app.js @@ -79,6 +79,7 @@ const App = () => { useEffect( () => { const allNotices = Array.from( document.querySelectorAll( ".notice-yoast" ) ); + allNotices.forEach(notice => notice.remove()); setNotices( allNotices ); }, [] ); From 977144680058a45a5bbd0ac3ba91a53c00128a55 Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Wed, 18 Sep 2024 16:41:16 +0300 Subject: [PATCH 167/313] Instantly hide notices when dismissed to keep the same UX as before --- src/integrations/admin/deactivated-premium-integration.php | 5 ++--- .../admin/first-time-configuration-notice-integration.php | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/integrations/admin/deactivated-premium-integration.php b/src/integrations/admin/deactivated-premium-integration.php index da18dab5db2..40516b137c6 100644 --- a/src/integrations/admin/deactivated-premium-integration.php +++ b/src/integrations/admin/deactivated-premium-integration.php @@ -112,9 +112,8 @@ function dismiss_premium_deactivated_notice(){ 'action': 'dismiss_premium_deactivated_notice', }; - jQuery.post( ajaxurl, data, function( response ) { - jQuery( '#yoast-premium-deactivated-notice' ).hide(); - }); + jQuery( '#yoast-premium-deactivated-notice' ).hide(); + jQuery.post( ajaxurl, data, function( response ) {}); } jQuery( document ).ready( function() { diff --git a/src/integrations/admin/first-time-configuration-notice-integration.php b/src/integrations/admin/first-time-configuration-notice-integration.php index 5b6125fc866..9abfde5d11d 100644 --- a/src/integrations/admin/first-time-configuration-notice-integration.php +++ b/src/integrations/admin/first-time-configuration-notice-integration.php @@ -138,13 +138,12 @@ public function first_time_configuration_notice() { echo ''; From 1d3f78df60c5abdd7c03bc1d970d054d2988617c Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 19 Sep 2024 09:23:25 +0300 Subject: [PATCH 168/313] feat: add collapsible for hidden alerts --- .../components/hidden-alerts-collapsible.js | 37 +++++++++++++++++++ packages/js/src/dashboard/components/index.js | 1 + .../src/dashboard/components/notifications.js | 21 +++++++++-- .../js/src/dashboard/components/problems.js | 18 ++++++++- .../js/src/dashboard/routes/alert-center.js | 2 +- 5 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 packages/js/src/dashboard/components/hidden-alerts-collapsible.js diff --git a/packages/js/src/dashboard/components/hidden-alerts-collapsible.js b/packages/js/src/dashboard/components/hidden-alerts-collapsible.js new file mode 100644 index 00000000000..7cc69f5cf92 --- /dev/null +++ b/packages/js/src/dashboard/components/hidden-alerts-collapsible.js @@ -0,0 +1,37 @@ + +import PropTypes from "prop-types"; +import { Disclosure } from "@headlessui/react"; +import { ChevronDownIcon } from "@heroicons/react/outline"; + +/** + * @param {string} label The label of the collapsible. + * @param {ReactNode} children The children of the collapsible. + * @returns {JSX.Element} The hidden alerts collapsible component. + */ +export const HiddenAlertsCollapsible = ( { label, children } ) => { + return ( + + { ( { open } ) => ( +
+ +
{ label }
+ +
+ + { children } + +
+ ) } +
+ ); +}; + +HiddenAlertsCollapsible.propTypes = { + label: PropTypes.string, + children: PropTypes.node, +}; + diff --git a/packages/js/src/dashboard/components/index.js b/packages/js/src/dashboard/components/index.js index e5264183e49..b3b79c5b31b 100644 --- a/packages/js/src/dashboard/components/index.js +++ b/packages/js/src/dashboard/components/index.js @@ -3,3 +3,4 @@ export { Notifications } from "./notifications"; export { Problems } from "./problems"; export { List } from "./list"; export { BoxTitle } from "./box-title"; +export { HiddenAlertsCollapsible } from "./hidden-alerts-collapsible"; diff --git a/packages/js/src/dashboard/components/notifications.js b/packages/js/src/dashboard/components/notifications.js index 7eff52d74d8..2bf5ba5db22 100644 --- a/packages/js/src/dashboard/components/notifications.js +++ b/packages/js/src/dashboard/components/notifications.js @@ -1,8 +1,9 @@ -import { __ } from "@wordpress/i18n"; +import { __, _n } from "@wordpress/i18n"; import { BellIcon } from "@heroicons/react/outline"; import { Paper } from "@yoast/ui-library"; import { List } from "./list"; import { BoxTitle } from "./box-title"; +import { HiddenAlertsCollapsible } from "./hidden-alerts-collapsible"; /** * @returns {JSX.Element} The notifications component. @@ -16,10 +17,24 @@ export const Notifications = () => { message: __( "You have a new notification from Yoast SEO. Click here to read it.", "wordpress-seo" ), }, ]; + + const hiddenNotificatins = 1; + + const hiddenNotificationLabel = _n( + "hidden notification.", + "hidden notifications.", + hiddenNotificatins, + "wordpress-seo" + ); + return ( - + - ); }; diff --git a/packages/js/src/dashboard/components/problems.js b/packages/js/src/dashboard/components/problems.js index 97cf1b405c2..3276333f495 100644 --- a/packages/js/src/dashboard/components/problems.js +++ b/packages/js/src/dashboard/components/problems.js @@ -1,8 +1,9 @@ -import { __ } from "@wordpress/i18n"; +import { __, _n } from "@wordpress/i18n"; import { ExclamationCircleIcon } from "@heroicons/react/outline"; import { Paper } from "@yoast/ui-library"; import { List } from "./list"; import { BoxTitle } from "./box-title"; +import { HiddenAlertsCollapsible } from "./hidden-alerts-collapsible"; /** * @returns {JSX.Element} The problems component. @@ -17,11 +18,24 @@ export const Problems = () => { }, ]; + const hiddenProblems = 1; + + const hiddenProblemsLabel = _n( + "hidden problem.", + "hidden problems.", + hiddenProblems, + "wordpress-seo" + ); + return ( - +

{ __( "We have detected the following issues that affect the SEO of your site.", "wordpress-seo" ) }

+ + +
); }; diff --git a/packages/js/src/dashboard/routes/alert-center.js b/packages/js/src/dashboard/routes/alert-center.js index bd2ca116ada..569188c8356 100644 --- a/packages/js/src/dashboard/routes/alert-center.js +++ b/packages/js/src/dashboard/routes/alert-center.js @@ -17,7 +17,7 @@ export const AlertCenter = () => {
-
+
From 3e4423710436a2368f462c09acc6a78775ca2976 Mon Sep 17 00:00:00 2001 From: Enrico Battocchi Date: Thu, 19 Sep 2024 09:37:08 +0200 Subject: [PATCH 169/313] Fix typos in readme.txt --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index f3ccdf78a4e..b4cfe90a35a 100644 --- a/readme.txt +++ b/readme.txt @@ -32,7 +32,7 @@ Yoast SEO offers comprehensive analysis tools that help elevate your content's S **Premium Yoast AI features** Get suggestions for your titles and descriptions at the click of a button. The [Yoast AI features](https://yoa.st/51c) save you time and optimize for higher click-through-rates. -* Yoast AI Generate enables users to generate meta descriptions and titles for your pages, blog posts and social posts. Great! Even better, when you also have [Yoast WooCommerce SEO](https://yoa.st/3rh), you can recieve suggestions for product SEO titles and descriptions too! The best part, if you don't like the 5 suggestions, you can generate five more at a click. +* Yoast AI Generate enables users to generate meta descriptions and titles for your pages, blog posts and social posts. Great! Even better, when you also have [Yoast WooCommerce SEO](https://yoa.st/3rh), you can receive suggestions for product SEO titles and descriptions too! The best part, if you don't like the 5 suggestions, you can generate five more at a click. * Yoast AI Optimize helps you optimize existing content for search engines. Optimize three of the assessments in the Yoast SEO Analysis; Keyphrase in introduction, Keyphrase distribution and Keyphrase density, with easy dismiss or apply options. From cb4f54e8cc802893caa6f169b1a67397ff1cf9c1 Mon Sep 17 00:00:00 2001 From: YoastBot Date: Thu, 19 Sep 2024 09:22:58 +0000 Subject: [PATCH 170/313] Add changelog --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index b4cfe90a35a..1984b256572 100644 --- a/readme.txt +++ b/readme.txt @@ -282,8 +282,8 @@ Yoast SEO 23.5 brings more enhancements and bugfixes. [Find more information abo #### Enhancements -* Improves our integration with cache plugins by preventing flushing their cache when not needed. * Improves analysis score feedback labels in the publish sections. +* Improves our integration with cache plugins by preventing flushing their cache when not needed. #### Bugfixes From 5a2fced68e7b9e11dffbd8f6de2a9e13271f0e17 Mon Sep 17 00:00:00 2001 From: YoastBot Date: Thu, 19 Sep 2024 09:23:00 +0000 Subject: [PATCH 171/313] Bump Gutenberg supported version. --- admin/class-gutenberg-compatibility.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/class-gutenberg-compatibility.php b/admin/class-gutenberg-compatibility.php index 74b55b1b6ff..31b42906f48 100644 --- a/admin/class-gutenberg-compatibility.php +++ b/admin/class-gutenberg-compatibility.php @@ -15,14 +15,14 @@ class WPSEO_Gutenberg_Compatibility { * * @var string */ - public const CURRENT_RELEASE = '19.1.0'; + public const CURRENT_RELEASE = '19.2.0'; /** * The minimally supported version of Gutenberg by the plugin. * * @var string */ - public const MINIMUM_SUPPORTED = '19.1.0'; + public const MINIMUM_SUPPORTED = '19.2.0'; /** * Holds the current version. From 28fc9e067dc2e2f9589631dfd55d805adc15a2a0 Mon Sep 17 00:00:00 2001 From: YoastBot Date: Thu, 19 Sep 2024 09:23:04 +0000 Subject: [PATCH 172/313] Bump version to 23.5-RC2 --- package.json | 2 +- tests/Unit/bootstrap.php | 2 +- wp-seo-main.php | 4 ++-- wp-seo.php | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 361156a266a..14739692183 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "webpack-bundle-analyzer": "^4.9.1" }, "yoast": { - "pluginVersion": "23.5-RC1" + "pluginVersion": "23.5-RC2" }, "version": "0.0.0" } diff --git a/tests/Unit/bootstrap.php b/tests/Unit/bootstrap.php index 54032e01604..10a7b6df018 100644 --- a/tests/Unit/bootstrap.php +++ b/tests/Unit/bootstrap.php @@ -40,7 +40,7 @@ \define( 'YOAST_VENDOR_PREFIX_DIRECTORY', 'vendor_prefixed' ); \define( 'YOAST_SEO_PHP_REQUIRED', '7.2.5' ); -\define( 'YOAST_SEO_WP_TESTED', '6.6.1' ); +\define( 'YOAST_SEO_WP_TESTED', '6.6.2' ); \define( 'YOAST_SEO_WP_REQUIRED', '6.4' ); if ( ! \defined( 'WPSEO_NAMESPACES' ) ) { diff --git a/wp-seo-main.php b/wp-seo-main.php index 335ee0e7ac4..e0ea080538f 100644 --- a/wp-seo-main.php +++ b/wp-seo-main.php @@ -15,7 +15,7 @@ * {@internal Nobody should be able to overrule the real version number as this can cause * serious issues with the options, so no if ( ! defined() ).}} */ -define( 'WPSEO_VERSION', '23.5-RC1' ); +define( 'WPSEO_VERSION', '23.5-RC2' ); if ( ! defined( 'WPSEO_PATH' ) ) { @@ -35,7 +35,7 @@ define( 'YOAST_VENDOR_PREFIX_DIRECTORY', 'vendor_prefixed' ); define( 'YOAST_SEO_PHP_REQUIRED', '7.2.5' ); -define( 'YOAST_SEO_WP_TESTED', '6.6.1' ); +define( 'YOAST_SEO_WP_TESTED', '6.6.2' ); define( 'YOAST_SEO_WP_REQUIRED', '6.4' ); if ( ! defined( 'WPSEO_NAMESPACES' ) ) { diff --git a/wp-seo.php b/wp-seo.php index ce752966023..b6036307db9 100644 --- a/wp-seo.php +++ b/wp-seo.php @@ -8,7 +8,7 @@ * * @wordpress-plugin * Plugin Name: Yoast SEO - * Version: 23.5-RC1 + * Version: 23.5-RC2 * Plugin URI: https://yoa.st/1uj * Description: The first true all-in-one SEO solution for WordPress, including on-page content analysis, XML sitemaps and much more. * Author: Team Yoast @@ -20,7 +20,7 @@ * Requires PHP: 7.2.5 * * WC requires at least: 7.1 - * WC tested up to: 9.2 + * WC tested up to: 9.3 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From 73cf34e33234b10b540853b9f109d03f6584257f Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Thu, 19 Sep 2024 12:23:51 +0300 Subject: [PATCH 173/313] Move the robot notice inside the new dashboard app --- packages/js/src/admin-global.js | 2 +- packages/js/src/dashboard/app.js | 5 ++-- packages/js/src/helpers/moveNotices.js | 14 +++++++++++ .../search-engines-discouraged-watcher.php | 23 +++++++++++-------- 4 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 packages/js/src/helpers/moveNotices.js diff --git a/packages/js/src/admin-global.js b/packages/js/src/admin-global.js index 307e7a625ed..1f57369859d 100644 --- a/packages/js/src/admin-global.js +++ b/packages/js/src/admin-global.js @@ -98,7 +98,7 @@ import jQuery from "jquery"; } ); // Dismiss the "search engines discouraged" admin notice. - jQuery( "button#robotsmessage-dismiss-button" ).on( "click", function() { + jQuery( 'body' ).on( 'click', 'button#robotsmessage-dismiss-button', function() { wpseoSetIgnore( "search_engines_discouraged_notice", "robotsmessage", jQuery( this ).data( "nonce" ) ).then( () => { // If we are on the dashboard, reload because we need to reload notifications as well. if ( window.location.href.includes( "page=wpseo_dashboard" ) ) { diff --git a/packages/js/src/dashboard/app.js b/packages/js/src/dashboard/app.js index 56bddbaa56e..259a6c324c0 100644 --- a/packages/js/src/dashboard/app.js +++ b/packages/js/src/dashboard/app.js @@ -11,6 +11,7 @@ import { Link, Route, Routes, useLocation } from "react-router-dom"; import { MenuItemLink, YoastLogo } from "../shared-admin/components"; import { useSelectDashboard } from "./hooks"; import { FirstTimeConfiguration } from "./routes"; +import { moveNotices } from "../helpers/moveNotices"; /** * @param {string} [idSuffix] Extra id suffix. Can prevent double IDs on the page. @@ -78,8 +79,8 @@ const App = () => { const [ notices, setNotices ] = useState( [] ); useEffect( () => { - const allNotices = Array.from( document.querySelectorAll( ".notice-yoast" ) ); - allNotices.forEach(notice => notice.remove()); + const allNotices = moveNotices(); + setNotices( allNotices ); }, [] ); diff --git a/packages/js/src/helpers/moveNotices.js b/packages/js/src/helpers/moveNotices.js new file mode 100644 index 00000000000..cca15022eec --- /dev/null +++ b/packages/js/src/helpers/moveNotices.js @@ -0,0 +1,14 @@ +/** + * Gets Yoast Notices and deletes them from their original place. + * + * @returns {Array} The now-deleted Yoast Notices. + */ +export function moveNotices() { + const noticeYoastNotices = Array.from( document.querySelectorAll( ".notice-yoast" ) ); + noticeYoastNotices.forEach( notice => notice.remove() ); + + const robotNotices = Array.from( document.querySelectorAll( "#robotsmessage" ) ); + robotNotices.forEach( notice => notice.remove() ); + + return [ ...noticeYoastNotices, ...robotNotices ]; +} diff --git a/src/integrations/watchers/search-engines-discouraged-watcher.php b/src/integrations/watchers/search-engines-discouraged-watcher.php index 36f27583fcc..2c2b360b41d 100644 --- a/src/integrations/watchers/search-engines-discouraged-watcher.php +++ b/src/integrations/watchers/search-engines-discouraged-watcher.php @@ -2,6 +2,7 @@ namespace Yoast\WP\SEO\Integrations\Watchers; +use Yoast\WP\SEO\Conditionals\New_Dashboard_Ui_Conditional; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Helpers\Capability_Helper; use Yoast\WP\SEO\Helpers\Current_Page_Helper; @@ -82,12 +83,13 @@ public function __construct( Options_Helper $options_helper, Capability_Helper $capability_helper ) { - $this->notification_center = $notification_center; - $this->notification_helper = $notification_helper; - $this->current_page_helper = $current_page_helper; - $this->options_helper = $options_helper; - $this->capability_helper = $capability_helper; - $this->presenter = new Search_Engines_Discouraged_Presenter(); + $this->notification_center = $notification_center; + $this->notification_helper = $notification_helper; + $this->current_page_helper = $current_page_helper; + $this->options_helper = $options_helper; + $this->capability_helper = $capability_helper; + $this->presenter = new Search_Engines_Discouraged_Presenter(); + $this->new_dashboard_conditional = new New_Dashboard_Ui_Conditional(); } /** @@ -197,7 +199,7 @@ protected function should_show_search_engines_discouraged_notice() { $this->current_page_helper->is_yoast_seo_page() || \in_array( $this->current_page_helper->get_current_admin_page(), $pages_to_show_notice, true ) ) - && $this->current_page_helper->get_current_yoast_seo_page() !== 'wpseo_dashboard' + && ( ( $this->new_dashboard_conditional )->is_met() || $this->current_page_helper->get_current_yoast_seo_page() !== 'wpseo_dashboard' ) ); } @@ -207,11 +209,14 @@ protected function should_show_search_engines_discouraged_notice() { * @return void */ protected function show_search_engines_discouraged_notice() { + $yoast_class = ( $this->new_dashboard_conditional )->is_met() ? ' notice-yoast' : ''; + // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Output from present() is considered safe. \printf( - '
%1$s
', - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output from present() is considered safe. + '
%2$s
', + $yoast_class, $this->presenter->present() ); + // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped } /** From 8bf9b476ff030e11ad6a791fac2126e3b9060c15 Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Thu, 19 Sep 2024 13:17:57 +0300 Subject: [PATCH 174/313] Fix margins of notices --- packages/js/src/dashboard/app.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/js/src/dashboard/app.js b/packages/js/src/dashboard/app.js index 9b8d3158d45..5b1e65d697c 100644 --- a/packages/js/src/dashboard/app.js +++ b/packages/js/src/dashboard/app.js @@ -80,12 +80,13 @@ const App = () => {
+ { notices.length > 0 &&
{ + notices.map( ( notice, index ) => ( +
+ ) ) + } +
}
- { - notices.map( ( notice, index ) => ( -
- ) ) - }
Date: Thu, 19 Sep 2024 13:18:15 +0300 Subject: [PATCH 175/313] Fix regression of old styling of robots notice --- .../watchers/search-engines-discouraged-watcher.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/integrations/watchers/search-engines-discouraged-watcher.php b/src/integrations/watchers/search-engines-discouraged-watcher.php index 2c2b360b41d..04e7d01340b 100644 --- a/src/integrations/watchers/search-engines-discouraged-watcher.php +++ b/src/integrations/watchers/search-engines-discouraged-watcher.php @@ -209,7 +209,7 @@ protected function should_show_search_engines_discouraged_notice() { * @return void */ protected function show_search_engines_discouraged_notice() { - $yoast_class = ( $this->new_dashboard_conditional )->is_met() ? ' notice-yoast' : ''; + $yoast_class = ( ( $this->new_dashboard_conditional )->is_met() && $this->current_page_helper->get_current_yoast_seo_page() === 'wpseo_dashboard' ) ? ' notice-yoast' : ''; // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Output from present() is considered safe. \printf( '
%2$s
', From ff1606bed350f0f4ccce9765f6fcb1486a687cdb Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 19 Sep 2024 15:14:40 +0200 Subject: [PATCH 176/313] Add notifications to script data. --- admin/class-config.php | 4 +- .../new-dashboard-page-integration.php | 21 +++- src/helpers/notification-helper.php | 103 ++++++++++++++++++ 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/admin/class-config.php b/admin/class-config.php index f8367968ae7..4346107573b 100644 --- a/admin/class-config.php +++ b/admin/class-config.php @@ -50,7 +50,7 @@ public function __construct() { public function init() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; - if ( in_array( $page, [ Settings_Integration::PAGE, Academy_Integration::PAGE, Support_Integration::PAGE ], true ) ) { + if ( in_array( $page, [ Settings_Integration::PAGE, Academy_Integration::PAGE, Support_Integration::PAGE, New_Dashboard_Page_Integration::PAGE ], true ) ) { // Bail, this is managed in the applicable integration. return; } @@ -104,7 +104,7 @@ public function config_page_scripts() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; - if ( in_array( $page, [ WPSEO_Admin::PAGE_IDENTIFIER, New_Dashboard_Page_Integration::PAGE, 'wpseo_workouts' ], true ) ) { + if ( in_array( $page, [ WPSEO_Admin::PAGE_IDENTIFIER, 'wpseo_workouts' ], true ) ) { wp_enqueue_media(); $script_data['userEditUrl'] = add_query_arg( 'user_id', '{user_id}', admin_url( 'user-edit.php' ) ); diff --git a/src/dashboard/user-interface/new-dashboard-page-integration.php b/src/dashboard/user-interface/new-dashboard-page-integration.php index 9ddcb604197..b61ffb7660c 100644 --- a/src/dashboard/user-interface/new-dashboard-page-integration.php +++ b/src/dashboard/user-interface/new-dashboard-page-integration.php @@ -6,6 +6,7 @@ use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\New_Dashboard_Ui_Conditional; use Yoast\WP\SEO\Helpers\Current_Page_Helper; +use Yoast\WP\SEO\Helpers\Notification_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; @@ -20,6 +21,13 @@ class New_Dashboard_Page_Integration implements Integration_Interface { */ public const PAGE = 'wpseo_dashboard'; + /** + * The notification helper. + * + * @var Notification_Helper + */ + protected $notification_helper; + /** * Holds the WPSEO_Admin_Asset_Manager. * @@ -55,17 +63,20 @@ class New_Dashboard_Page_Integration implements Integration_Interface { * @param Current_Page_Helper $current_page_helper The Current_Page_Helper. * @param Product_Helper $product_helper The Product_Helper. * @param Short_Link_Helper $shortlink_helper The Short_Link_Helper. + * @param Notification_Helper $notification_helper The Notification_Helper. */ public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Current_Page_Helper $current_page_helper, Product_Helper $product_helper, - Short_Link_Helper $shortlink_helper + Short_Link_Helper $shortlink_helper, + Notification_Helper $notification_helper ) { $this->asset_manager = $asset_manager; $this->current_page_helper = $current_page_helper; $this->product_helper = $product_helper; $this->shortlink_helper = $shortlink_helper; + $this->notification_helper = $notification_helper; } /** @@ -141,7 +152,7 @@ public function enqueue_assets() { \wp_enqueue_media(); $this->asset_manager->enqueue_script( 'new-dashboard' ); $this->asset_manager->enqueue_style( 'new-dashboard' ); - $this->asset_manager->localize_script( 'dashboard', 'wpseoScriptData', $this->get_script_data() ); + $this->asset_manager->localize_script( 'new-dashboard', 'wpseoScriptData', $this->get_script_data() ); } /** @@ -151,7 +162,7 @@ public function enqueue_assets() { */ private function get_script_data() { return [ - 'preferences' => [ + 'preferences' => [ 'isPremium' => $this->product_helper->is_premium(), 'isRtl' => \is_rtl(), 'pluginUrl' => \plugins_url( '', \WPSEO_FILE ), @@ -160,7 +171,9 @@ private function get_script_data() { 'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2', ], ], - 'linkParams' => $this->shortlink_helper->get_query_params(), + 'linkParams' => $this->shortlink_helper->get_query_params(), + 'problems' => $this->notification_helper->get_problems(), + 'notifications' => $this->notification_helper->get_notifications(), ]; } } diff --git a/src/helpers/notification-helper.php b/src/helpers/notification-helper.php index b84d388b8af..c06ca22dd05 100644 --- a/src/helpers/notification-helper.php +++ b/src/helpers/notification-helper.php @@ -22,4 +22,107 @@ class Notification_Helper { public function restore_notification( Yoast_Notification $notification ) { return Yoast_Notification_Center::restore_notification( $notification ); } + + /** + * Return the notifications sorted on type and priority. (wrapper function) + * + * @codeCoverageIgnore + * + * @return array|Yoast_Notification[] Sorted Notifications + */ + public function get_sorted_notifications() { + $notification_center = Yoast_Notification_Center::get(); + + return $notification_center->get_sorted_notifications(); + } + + /** + * Check if the user has dismissed a notification. (wrapper function) + * + * @param Yoast_Notification $notification The notification to check for dismissal. + * @param int|null $user_id User ID to check on. + * + * @codeCoverageIgnore + * + * @return bool + */ + private function is_notification_dismissed( Yoast_Notification $notification, $user_id = null ) { + return Yoast_Notification_Center::is_notification_dismissed( $notification, $user_id ); + } + + /** + * Parses all the notifications to an array with just warnings notifications, and splitting them between dismissed + * and active. + * + * @return array + */ + public function get_notifications(): array { + $all_notifications = $this->get_sorted_notifications(); + $notifications = \array_filter( + $all_notifications, + static function ( $notification ) { + return $notification->get_type() !== 'error'; + } + ); + $dismissed_notifications = \array_filter( + $notifications, + function ( $notification ) { + return $this->is_notification_dismissed( $notification ); + } + ); + $active_notifications = \array_diff( $notifications, $dismissed_notifications ); + + return [ + 'dismissed' => \array_map( + static function ( $notification ) { + return $notification->to_array(); + }, + $dismissed_notifications + ), + 'active' => \array_map( + static function ( $notification ) { + return $notification->to_array(); + }, + $active_notifications + ), + ]; + } + + /** + * Parses all the notifications to an array with just error notifications, and splitting them between dismissed and + * active. + * + * @return array + */ + public function get_problems(): array { + $all_notifications = $this->get_sorted_notifications(); + $problems = \array_filter( + $all_notifications, + static function ( $notification ) { + return $notification->get_type() === 'error'; + } + ); + $dismissed_problems = \array_filter( + $problems, + function ( $notification ) { + return $this->is_notification_dismissed( $notification ); + } + ); + $active_problems = \array_diff( $problems, $dismissed_problems ); + + return [ + 'dismissed' => \array_map( + static function ( $notification ) { + return $notification->to_array(); + }, + $dismissed_problems + ), + 'active' => \array_map( + static function ( $notification ) { + return $notification->to_array(); + }, + $active_problems + ), + ]; + } } From 8f4e69cb05de8e5caeb72159c50bbd8d91283113 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 19 Sep 2024 15:17:31 +0200 Subject: [PATCH 177/313] Update test. --- .../New_Dashboard_Page_Integration_Test.php | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php index ef377978bdc..189ba56a20e 100644 --- a/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php +++ b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php @@ -9,6 +9,7 @@ use Yoast\WP\SEO\Conditionals\New_Dashboard_Ui_Conditional; use Yoast\WP\SEO\Dashboard\User_Interface\New_Dashboard_Page_Integration; use Yoast\WP\SEO\Helpers\Current_Page_Helper; +use Yoast\WP\SEO\Helpers\Notification_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Integrations\Academy_Integration; @@ -51,6 +52,13 @@ final class New_Dashboard_Page_Integration_Test extends TestCase { */ private $shortlink_helper; + /** + * Holds the Notification_Helper. + * + * @var Notification_Helper + */ + private $notifications_helper; + /** * The class under test. * @@ -70,12 +78,14 @@ public function set_up() { $this->current_page_helper = Mockery::mock( Current_Page_Helper::class ); $this->product_helper = Mockery::mock( Product_Helper::class ); $this->shortlink_helper = Mockery::mock( Short_Link_Helper::class ); + $this->notifications_helper = Mockery::mock( Notification_Helper::class ); $this->instance = new New_Dashboard_Page_Integration( $this->asset_manager, $this->current_page_helper, $this->product_helper, - $this->shortlink_helper + $this->shortlink_helper, + $this->notifications_helper ); } @@ -272,6 +282,16 @@ public function expect_get_script_data() { ->once() ->andReturn( $link_params ); + $this->notifications_helper + ->expects( 'get_problems' ) + ->once() + ->andReturn( [] ); + + $this->notifications_helper + ->expects( 'get_notifications' ) + ->once() + ->andReturn( [] ); + return $link_params; } } From 7da2e102ae0846d71e5b2c67de1c20c2d8092a02 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 19 Sep 2024 15:28:47 +0200 Subject: [PATCH 178/313] Update test. and class-config. --- admin/class-config.php | 5 +++-- .../new-dashboard-page-integration.php | 1 + .../New_Dashboard_Page_Integration_Test.php | 12 +++++++----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/admin/class-config.php b/admin/class-config.php index 4346107573b..cc908971072 100644 --- a/admin/class-config.php +++ b/admin/class-config.php @@ -6,7 +6,7 @@ */ use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; -use Yoast\WP\SEO\Dashboard\User_Interface\New_Dashboard_Page_Integration; +use Yoast\WP\SEO\Conditionals\New_Dashboard_Ui_Conditional; use Yoast\WP\SEO\Integrations\Academy_Integration; use Yoast\WP\SEO\Integrations\Settings_Integration; use Yoast\WP\SEO\Integrations\Support_Integration; @@ -48,9 +48,10 @@ public function __construct() { * @return void */ public function init() { + $conditional = new New_Dashboard_Ui_Conditional(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; - if ( in_array( $page, [ Settings_Integration::PAGE, Academy_Integration::PAGE, Support_Integration::PAGE, New_Dashboard_Page_Integration::PAGE ], true ) ) { + if ( in_array( $page, [ Settings_Integration::PAGE, Academy_Integration::PAGE, Support_Integration::PAGE ], true ) || $conditional->is_met() ) { // Bail, this is managed in the applicable integration. return; } diff --git a/src/dashboard/user-interface/new-dashboard-page-integration.php b/src/dashboard/user-interface/new-dashboard-page-integration.php index b61ffb7660c..3c405c6937b 100644 --- a/src/dashboard/user-interface/new-dashboard-page-integration.php +++ b/src/dashboard/user-interface/new-dashboard-page-integration.php @@ -172,6 +172,7 @@ private function get_script_data() { ], ], 'linkParams' => $this->shortlink_helper->get_query_params(), + 'userEditUrl' => \add_query_arg( 'user_id', '{user_id}', \admin_url( 'user-edit.php' ) ), 'problems' => $this->notification_helper->get_problems(), 'notifications' => $this->notification_helper->get_notifications(), ]; diff --git a/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php index 189ba56a20e..9fdf7ec4f31 100644 --- a/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php +++ b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php @@ -74,11 +74,11 @@ final class New_Dashboard_Page_Integration_Test extends TestCase { public function set_up() { $this->stubTranslationFunctions(); - $this->asset_manager = Mockery::mock( WPSEO_Admin_Asset_Manager::class ); - $this->current_page_helper = Mockery::mock( Current_Page_Helper::class ); - $this->product_helper = Mockery::mock( Product_Helper::class ); - $this->shortlink_helper = Mockery::mock( Short_Link_Helper::class ); - $this->notifications_helper = Mockery::mock( Notification_Helper::class ); + $this->asset_manager = Mockery::mock( WPSEO_Admin_Asset_Manager::class ); + $this->current_page_helper = Mockery::mock( Current_Page_Helper::class ); + $this->product_helper = Mockery::mock( Product_Helper::class ); + $this->shortlink_helper = Mockery::mock( Short_Link_Helper::class ); + $this->notifications_helper = Mockery::mock( Notification_Helper::class ); $this->instance = new New_Dashboard_Page_Integration( $this->asset_manager, @@ -230,6 +230,8 @@ public function test_enqueue_assets() { ->once(); Monkey\Functions\expect( 'wp_enqueue_media' )->once(); + Monkey\Functions\expect( 'add_query_arg' )->once(); + Monkey\Functions\expect( 'admin_url' )->once(); $this->asset_manager ->expects( 'enqueue_script' ) From 2d87ca4f4a52c44aa4f6770a96b5712fd48ba3dc Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 19 Sep 2024 15:31:10 +0200 Subject: [PATCH 179/313] Update to use conditional. --- admin/class-config.php | 5 +++-- .../User_Interface/New_Dashboard_Page_Integration_Test.php | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/admin/class-config.php b/admin/class-config.php index 4346107573b..cc908971072 100644 --- a/admin/class-config.php +++ b/admin/class-config.php @@ -6,7 +6,7 @@ */ use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; -use Yoast\WP\SEO\Dashboard\User_Interface\New_Dashboard_Page_Integration; +use Yoast\WP\SEO\Conditionals\New_Dashboard_Ui_Conditional; use Yoast\WP\SEO\Integrations\Academy_Integration; use Yoast\WP\SEO\Integrations\Settings_Integration; use Yoast\WP\SEO\Integrations\Support_Integration; @@ -48,9 +48,10 @@ public function __construct() { * @return void */ public function init() { + $conditional = new New_Dashboard_Ui_Conditional(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; - if ( in_array( $page, [ Settings_Integration::PAGE, Academy_Integration::PAGE, Support_Integration::PAGE, New_Dashboard_Page_Integration::PAGE ], true ) ) { + if ( in_array( $page, [ Settings_Integration::PAGE, Academy_Integration::PAGE, Support_Integration::PAGE ], true ) || $conditional->is_met() ) { // Bail, this is managed in the applicable integration. return; } diff --git a/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php index a6f98f9a376..e47496dabb6 100644 --- a/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php +++ b/tests/Unit/Dashboard/User_Interface/New_Dashboard_Page_Integration_Test.php @@ -256,7 +256,8 @@ public function expect_get_script_data() { ->andReturn( false ); Monkey\Functions\expect( 'is_rtl' )->once()->andReturn( false ); - + Monkey\Functions\expect( 'add_query_arg' )->once(); + Monkey\Functions\expect( 'admin_url' )->once(); Monkey\Functions\expect( 'plugins_url' ) ->once() ->andReturn( 'http://basic.wordpress.test/wp-content/worspress-seo' ); From 3d4606db3b207ce33b1dc554cfa067ee5c632efb Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 19 Sep 2024 15:46:43 +0200 Subject: [PATCH 180/313] js? --- packages/js/src/dashboard/components/sidebar-recommendations.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/js/src/dashboard/components/sidebar-recommendations.js b/packages/js/src/dashboard/components/sidebar-recommendations.js index a99adf8e5bc..94bea5e95bb 100644 --- a/packages/js/src/dashboard/components/sidebar-recommendations.js +++ b/packages/js/src/dashboard/components/sidebar-recommendations.js @@ -1,6 +1,5 @@ import { AcademyUpsellCard, PremiumUpsellCard, RecommendationsSidebar } from "../../shared-admin/components"; import { useSelectDashboard } from "../hooks"; -import { get } from "lodash"; /** * @returns {JSX.Element} The sidebar recommendations. */ From f0630ee91d6fb1666e13d9a4ebfa90ea99ef2a20 Mon Sep 17 00:00:00 2001 From: Thijs van der heijden Date: Thu, 19 Sep 2024 16:03:16 +0200 Subject: [PATCH 181/313] js? --- packages/js/src/shared-admin/components/premium-upsell-list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/src/shared-admin/components/premium-upsell-list.js b/packages/js/src/shared-admin/components/premium-upsell-list.js index 4f3bfb24124..035055d162a 100644 --- a/packages/js/src/shared-admin/components/premium-upsell-list.js +++ b/packages/js/src/shared-admin/components/premium-upsell-list.js @@ -1,5 +1,5 @@ import { ArrowNarrowRightIcon } from "@heroicons/react/outline"; -import { createInterpolateElement, useMemo } from "@wordpress/element"; +import { createInterpolateElement } from "@wordpress/element"; import { __, sprintf } from "@wordpress/i18n"; import { Button, Title, Paper } from "@yoast/ui-library"; import PropTypes from "prop-types"; From 476ff2ecb6a8cbd3c1d291761926396f284274fe Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Thu, 19 Sep 2024 17:18:17 +0300 Subject: [PATCH 182/313] feat: fix styling refactor comment clear property feat: deconstruct context feat: restore styling feat: add safety check for deconstructing the context fix padding in collapsible fix js doc comment fix margin fix collapsable --- .../src/dashboard/components/alert-title.js | 32 ++++++++++++++++ .../components/{list.js => alerts-list.js} | 24 ++++++------ .../js/src/dashboard/components/box-title.js | 37 ------------------- ...n-alerts-collapsible.js => collapsible.js} | 20 +++++----- packages/js/src/dashboard/components/index.js | 6 +-- .../src/dashboard/components/notifications.js | 35 +++++++++++------- .../js/src/dashboard/components/problems.js | 31 ++++++++++------ .../js/src/dashboard/routes/alert-center.js | 8 +++- 8 files changed, 105 insertions(+), 88 deletions(-) create mode 100644 packages/js/src/dashboard/components/alert-title.js rename packages/js/src/dashboard/components/{list.js => alerts-list.js} (69%) delete mode 100644 packages/js/src/dashboard/components/box-title.js rename packages/js/src/dashboard/components/{hidden-alerts-collapsible.js => collapsible.js} (58%) diff --git a/packages/js/src/dashboard/components/alert-title.js b/packages/js/src/dashboard/components/alert-title.js new file mode 100644 index 00000000000..8dc522f707e --- /dev/null +++ b/packages/js/src/dashboard/components/alert-title.js @@ -0,0 +1,32 @@ +import { useContext } from "@wordpress/element"; +import PropTypes from "prop-types"; +import classNames from "classnames"; +import { ExclamationCircleIcon } from "@heroicons/react/outline"; +import { Title } from "@yoast/ui-library"; +import { AlertsContext } from "../routes/alert-center"; + +/** + * + * @param {string} title The title of alerts. + * @param {number} counts The count of the alerts. + * + * @returns {JSX.Element} The alert title element. + */ +export const AlertTitle = ( { + title, + counts = 0, +} ) => { + const { Icon = ExclamationCircleIcon, iconClass = "" } = useContext( AlertsContext ); + + return ( +
+ + { title } { counts > 0 && `(${counts})` } +
+ ); +}; + +AlertTitle.propTypes = { + title: PropTypes.string, + counts: PropTypes.number, +}; diff --git a/packages/js/src/dashboard/components/list.js b/packages/js/src/dashboard/components/alerts-list.js similarity index 69% rename from packages/js/src/dashboard/components/list.js rename to packages/js/src/dashboard/components/alerts-list.js index de036f28f83..74b38e48c84 100644 --- a/packages/js/src/dashboard/components/list.js +++ b/packages/js/src/dashboard/components/alerts-list.js @@ -1,36 +1,35 @@ +import { useContext } from "@wordpress/element"; import PropTypes from "prop-types"; import { Button } from "@yoast/ui-library"; import { EyeOffIcon, EyeIcon } from "@heroicons/react/outline"; import classNames from "classnames"; +import { AlertsContext } from "../routes/alert-center"; /** * - * @param {string} bulletColor The color of the bullet. - * @param {Array} items The list of items. + * @param {Object[]} items The list of items. * @param {boolean} hidden Whether the items are hidden or not. * * @returns {JSX.Element} The list component. */ -export const List = ( { bulletColor = "red", items = [], hidden = false } ) => { - const colors = { - red: "yst-fill-red-500", - blue: "yst-fill-blue-500", - }; +export const AlertsList = ( { items = [], hidden = false } ) => { + const { bulletClass = "" } = useContext( AlertsContext ); + const Eye = hidden ? EyeIcon : EyeOffIcon; return ( -
    +
      { items.map( ( item, index ) => (
    • ' . '' . '
'; @@ -126,8 +128,9 @@ public function test_dismissble_notice() { . '' . '

title

' . '
' + . '
' . '

content

' - . '
' + . '
' . '
'; Monkey\Functions\expect( 'esc_html' )->andReturn( '' ); @@ -155,8 +158,9 @@ public function test_dismissble_notice_with_image() { . '' . '

title

' . '
' + . '
' . '

content

' - . '
' + . '
' . '' . ''; @@ -187,9 +191,10 @@ public function test_dismissble_notice_with_image_and_button() { . '' . '

title

' . '' + . '
' . '

content

' . '

Some text

' - . '
' + . '' . '' . ''; From b06fcecc988e555c7690e7d5f4dd71241b0ce138 Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Fri, 20 Sep 2024 11:51:55 +0300 Subject: [PATCH 190/313] Move migration error notice to the new dashboard --- packages/js/src/helpers/moveNotices.js | 2 +- src/presenters/admin/migration-error-presenter.php | 8 +++++++- src/presenters/admin/notice-presenter.php | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/js/src/helpers/moveNotices.js b/packages/js/src/helpers/moveNotices.js index 39d7cb4d0a4..d33889c024e 100644 --- a/packages/js/src/helpers/moveNotices.js +++ b/packages/js/src/helpers/moveNotices.js @@ -13,7 +13,7 @@ export function moveNotices() { migratedNotices.forEach( notice => notice.remove() ); const ids = allNotices.map( notice => notice.id ); - const headers = allNotices.map( notice => notice.querySelector( ".notice-yoast__header-heading" ) ); + const headers = allNotices.map( notice => notice.querySelector( ".yoast-notice-migrated-header" ) ); const content = allNotices.map( notice => notice.querySelector( ".notice-yoast-content" ) ); const buttons = allNotices.map( notice => notice.querySelector( "button.notice-dismiss" ) ); diff --git a/src/presenters/admin/migration-error-presenter.php b/src/presenters/admin/migration-error-presenter.php index 9ef789fbb55..fdbae72eaac 100644 --- a/src/presenters/admin/migration-error-presenter.php +++ b/src/presenters/admin/migration-error-presenter.php @@ -37,6 +37,11 @@ public function __construct( $migration_error ) { * @return string The error HTML. */ public function present() { + $header = \sprintf( + /* translators: %s: Yoast SEO. */ + \esc_html__( '%s cannot create database tables', 'wordpress-seo' ), + 'Yoast SEO' + ); $message = \sprintf( /* translators: %s: Yoast SEO. */ \esc_html__( '%s had problems creating the database tables needed to speed up your site.', 'wordpress-seo' ), @@ -61,7 +66,8 @@ public function present() { ); return \sprintf( - '

%1$s

%2$s

%3$s

%4$s
', + '

%1$s

%2$s

%3$s

%4$s

%5$s
', + $header, $message, $support, $reassurance, diff --git a/src/presenters/admin/notice-presenter.php b/src/presenters/admin/notice-presenter.php index 78a34a3be39..1135c42090f 100644 --- a/src/presenters/admin/notice-presenter.php +++ b/src/presenters/admin/notice-presenter.php @@ -104,7 +104,7 @@ public function present() { $out .= '
'; $out .= ''; $out .= \sprintf( - '

%s

', + '

%s

', \esc_html( $this->title ) ); $out .= '
'; From 91a176a59b153b254bd742e7a3b6e1104ece418c Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Fri, 20 Sep 2024 12:08:01 +0300 Subject: [PATCH 191/313] Fix stylings of links in new notices --- css/src/new-dashboard.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/css/src/new-dashboard.css b/css/src/new-dashboard.css index cb5e40b3beb..c2f27d5a103 100644 --- a/css/src/new-dashboard.css +++ b/css/src/new-dashboard.css @@ -29,6 +29,10 @@ body.toplevel_page_wpseo_dashboard { display: none; } + .yoast-dashboard-notice a{ + font-weight: 500; + } + @apply yst-bg-slate-100; /* Move WP footer behind our content. */ From 9d5ee068427f9f718923fe5960fa9c89c22a90dc Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Fri, 20 Sep 2024 14:46:36 +0300 Subject: [PATCH 192/313] Translate buttons to yoast buttons --- packages/js/src/helpers/moveNotices.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/js/src/helpers/moveNotices.js b/packages/js/src/helpers/moveNotices.js index d33889c024e..92507d176c3 100644 --- a/packages/js/src/helpers/moveNotices.js +++ b/packages/js/src/helpers/moveNotices.js @@ -17,6 +17,18 @@ export function moveNotices() { const content = allNotices.map( notice => notice.querySelector( ".notice-yoast-content" ) ); const buttons = allNotices.map( notice => notice.querySelector( "button.notice-dismiss" ) ); + // Transform the buttons to Yoast buttons. + content.forEach( noticeContent => { + if ( noticeContent ) { + noticeContent.querySelectorAll( "a.button" ).forEach( button => { + button.classList.remove( "button" ); + button.classList.add( "yst-button" ); + button.classList.add( "yst-button--primary" ); + button.classList.add( "yst-mt-4" ); + } ); + } + } ); + const notices = allNotices.map( ( notice, index ) => ( { notice: notice, id: ids[ index ], From eed2c75a54c3503b349f4d1b3a0c8d7705df7c05 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 20 Sep 2024 14:46:52 +0300 Subject: [PATCH 193/313] removes translations for dummy data --- packages/js/src/dashboard/components/notifications.js | 4 ++-- packages/js/src/dashboard/components/problems.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/js/src/dashboard/components/notifications.js b/packages/js/src/dashboard/components/notifications.js index feba3271f6f..8a05dc1c30c 100644 --- a/packages/js/src/dashboard/components/notifications.js +++ b/packages/js/src/dashboard/components/notifications.js @@ -12,10 +12,10 @@ import { AlertsContext } from "../routes/alert-center"; export const Notifications = () => { const notificationsAlertsList = [ { - message: __( "Your site is not connected to your MyYoast account. Connect your site to get access to all the features.", "wordpress-seo" ), + message: "Your site is not connected to your MyYoast account. Connect your site to get access to all the features.", }, { - message: __( "You have a new notification from Yoast SEO. Click here to read it.", "wordpress-seo" ), + message: "You have a new notification from Yoast SEO. Click here to read it.", }, ]; diff --git a/packages/js/src/dashboard/components/problems.js b/packages/js/src/dashboard/components/problems.js index 36a4353fda7..b6c562b65a0 100644 --- a/packages/js/src/dashboard/components/problems.js +++ b/packages/js/src/dashboard/components/problems.js @@ -12,10 +12,10 @@ import { AlertsContext } from "../routes/alert-center"; export const Problems = () => { const problemsList = [ { - message: __( "Huge SEO issue: You're blocking access to robots. If you want search engines to show this site in their results, you must go to your Reading Settings and uncheck the box for Search Engine Visibility. I don't want this site to show in the search results.", "wordpress-seo" ), + message: "Huge SEO issue: You're blocking access to robots. If you want search engines to show this site in their results, you must go to your Reading Settings and uncheck the box for Search Engine Visibility. I don't want this site to show in the search results.", }, { - message: __( "You still have the default WordPress tagline, even an empty one is probably better. You can fix this in the customizer.", "wordpress-seo" ), + message: "You still have the default WordPress tagline, even an empty one is probably better. You can fix this in the customizer.", }, ]; From 1ba30de7b5d7874ac0bff03bf0a5aae00ce6d5e0 Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 20 Sep 2024 14:59:18 +0300 Subject: [PATCH 194/313] refactor paper classes --- .../src/dashboard/components/notifications.js | 18 +++++++++-------- .../js/src/dashboard/components/problems.js | 20 ++++++++++--------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/js/src/dashboard/components/notifications.js b/packages/js/src/dashboard/components/notifications.js index 8a05dc1c30c..6064351795f 100644 --- a/packages/js/src/dashboard/components/notifications.js +++ b/packages/js/src/dashboard/components/notifications.js @@ -35,15 +35,17 @@ export const Notifications = () => { }; return ( - - - - + ); }; diff --git a/packages/js/src/dashboard/components/problems.js b/packages/js/src/dashboard/components/problems.js index b6c562b65a0..0c5487a2121 100644 --- a/packages/js/src/dashboard/components/problems.js +++ b/packages/js/src/dashboard/components/problems.js @@ -35,16 +35,18 @@ export const Problems = () => { }; return ( - - - -

{ __( "We have detected the following issues that affect the SEO of your site.", "wordpress-seo" ) }

- + + + + +

{ __( "We have detected the following issues that affect the SEO of your site.", "wordpress-seo" ) }

+ - - -
+ + +
+
); }; From d2d23dca1c4b9be939877d52b78d942064ea2a2b Mon Sep 17 00:00:00 2001 From: Vraja Das Date: Fri, 20 Sep 2024 15:08:56 +0300 Subject: [PATCH 195/313] separates alert center context --- packages/js/src/dashboard/components/alert-title.js | 2 +- packages/js/src/dashboard/components/alerts-list.js | 2 +- packages/js/src/dashboard/components/notifications.js | 2 +- packages/js/src/dashboard/components/problems.js | 2 +- packages/js/src/dashboard/contexts/alerts-context.js | 6 ++++++ packages/js/src/dashboard/routes/alert-center.js | 6 ------ 6 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 packages/js/src/dashboard/contexts/alerts-context.js diff --git a/packages/js/src/dashboard/components/alert-title.js b/packages/js/src/dashboard/components/alert-title.js index dab72c4b028..2e5901074fe 100644 --- a/packages/js/src/dashboard/components/alert-title.js +++ b/packages/js/src/dashboard/components/alert-title.js @@ -3,7 +3,7 @@ import PropTypes from "prop-types"; import classNames from "classnames"; import { ExclamationCircleIcon } from "@heroicons/react/outline"; import { Title } from "@yoast/ui-library"; -import { AlertsContext } from "../routes/alert-center"; +import { AlertsContext } from "../contexts/alerts-context"; /** * diff --git a/packages/js/src/dashboard/components/alerts-list.js b/packages/js/src/dashboard/components/alerts-list.js index 74b38e48c84..2046adaf6c5 100644 --- a/packages/js/src/dashboard/components/alerts-list.js +++ b/packages/js/src/dashboard/components/alerts-list.js @@ -3,7 +3,7 @@ import PropTypes from "prop-types"; import { Button } from "@yoast/ui-library"; import { EyeOffIcon, EyeIcon } from "@heroicons/react/outline"; import classNames from "classnames"; -import { AlertsContext } from "../routes/alert-center"; +import { AlertsContext } from "../contexts/alerts-context"; /** * diff --git a/packages/js/src/dashboard/components/notifications.js b/packages/js/src/dashboard/components/notifications.js index 6064351795f..bb5964a1f08 100644 --- a/packages/js/src/dashboard/components/notifications.js +++ b/packages/js/src/dashboard/components/notifications.js @@ -4,7 +4,7 @@ import { Paper } from "@yoast/ui-library"; import { AlertsList } from "./alerts-list"; import { AlertTitle } from "./alert-title"; import { Collapsible } from "./collapsible"; -import { AlertsContext } from "../routes/alert-center"; +import { AlertsContext } from "../contexts/alerts-context"; /** * @returns {JSX.Element} The notifications component. diff --git a/packages/js/src/dashboard/components/problems.js b/packages/js/src/dashboard/components/problems.js index 0c5487a2121..fd32c4e9cb7 100644 --- a/packages/js/src/dashboard/components/problems.js +++ b/packages/js/src/dashboard/components/problems.js @@ -4,7 +4,7 @@ import { Paper } from "@yoast/ui-library"; import { AlertsList } from "./alerts-list"; import { AlertTitle } from "./alert-title"; import { Collapsible } from "./collapsible"; -import { AlertsContext } from "../routes/alert-center"; +import { AlertsContext } from "../contexts/alerts-context"; /** * @returns {JSX.Element} The problems component. diff --git a/packages/js/src/dashboard/contexts/alerts-context.js b/packages/js/src/dashboard/contexts/alerts-context.js new file mode 100644 index 00000000000..a8ab3c90e50 --- /dev/null +++ b/packages/js/src/dashboard/contexts/alerts-context.js @@ -0,0 +1,6 @@ +import { createContext } from "@wordpress/element"; + +/** + * The context for the alerts. + */ +export const AlertsContext = createContext( { Icon: null, bulletClass: "", iconClass: "" } ); diff --git a/packages/js/src/dashboard/routes/alert-center.js b/packages/js/src/dashboard/routes/alert-center.js index 59537b2fc58..75f6b2ec0cd 100644 --- a/packages/js/src/dashboard/routes/alert-center.js +++ b/packages/js/src/dashboard/routes/alert-center.js @@ -1,12 +1,6 @@ import { __ } from "@wordpress/i18n"; import { Paper, Title } from "@yoast/ui-library"; import { Notifications, Problems } from "../components"; -import { createContext } from "@wordpress/element"; - -/** - * The context for the alerts. - */ -export const AlertsContext = createContext(); /** * @returns {JSX.Element} The dashboard content placeholder. From 54fa28e52780e023dfb8f69f70a09196bdc57275 Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Fri, 20 Sep 2024 15:14:31 +0300 Subject: [PATCH 196/313] Move activation failed notice to the new dashboard --- wp-seo-main.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/wp-seo-main.php b/wp-seo-main.php index 335ee0e7ac4..643b184447c 100644 --- a/wp-seo-main.php +++ b/wp-seo-main.php @@ -563,8 +563,14 @@ function yoast_wpseo_missing_filter_notice() { * @return void */ function yoast_wpseo_activation_failed_notice( $message ) { + $title = sprintf( + /* translators: %s: Yoast SEO. */ + esc_html__( '%s activation failed', 'wordpress-seo' ), + 'Yoast SEO' + ); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This function is only called in 3 places that are safe. - echo '

' . esc_html__( 'Activation failed:', 'wordpress-seo' ) . ' ' . strip_tags( $message, '' ) . '

'; + echo ''; } /** From cf2638b825de976e683790f877e7acc37cc70eda Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Fri, 20 Sep 2024 15:25:12 +0300 Subject: [PATCH 197/313] Improve styling of new headers in old notices --- css/src/admin-global.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/css/src/admin-global.css b/css/src/admin-global.css index 0bda03369d5..f2deec79f5c 100644 --- a/css/src/admin-global.css +++ b/css/src/admin-global.css @@ -817,6 +817,10 @@ body.folded .wpseo-admin-submit-fixed { color: #d63638; } +.yoast-notice-migrated-header { + margin-top: 10px; +} + .privacy-settings .notice-yoast { margin: 0 20px; } From b098b116681aa50efb282a4c4c062c08c33f0b6e Mon Sep 17 00:00:00 2001 From: Leonidas Milosis Date: Fri, 20 Sep 2024 15:40:00 +0300 Subject: [PATCH 198/313] Fix unit tests --- ...irst_Time_Configuration_Notice_Integration_Test.php | 4 ++-- .../Admin/Migration_Error_Integration_Test.php | 5 +++-- .../Admin/Migration_Error_Presenter_Test.php | 5 +++-- tests/Unit/Presenters/Admin/Notice_Presenter_Test.php | 10 +++++----- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/Unit/Integrations/Admin/First_Time_Configuration_Notice_Integration_Test.php b/tests/Unit/Integrations/Admin/First_Time_Configuration_Notice_Integration_Test.php index 05c0abecd9d..6133b0ad8a9 100644 --- a/tests/Unit/Integrations/Admin/First_Time_Configuration_Notice_Integration_Test.php +++ b/tests/Unit/Integrations/Admin/First_Time_Configuration_Notice_Integration_Test.php @@ -182,7 +182,7 @@ public function test_should_display_first_time_configuration_notice() { public static function first_time_configuration_notice_provider() { // In case of change in js, make sure to match the tabs and line breaks for this test to pass (avoid 4 spaces as tab). - $default_message = '

First-time SEO configuration

Get started quickly with the Yoast SEO First-time configuration and configure Yoast SEO with the optimal SEO settings for your site!

'; - $alternate_message = '

First-time SEO configuration

We noticed that you haven\'t fully configured Yoast SEO yet. Optimize your SEO settings even further by using our improved First-time configuration.