readme.txt000064400000054437150211766350006565 0ustar00=== All in One SEO – Powerful SEO Plugin to Boost SEO Rankings & Increase Traffic === Contributors: aioseo, smub, benjaminprojas Tags: SEO, Google Search Console, XML Sitemap, meta description, schema Tested up to: 6.8 Requires at least: 5.3 Requires PHP: 7.0 Stable tag: 4.8.2 License: GPLv3 or later License URI: https://www.gnu.org/licenses/gpl-3.0.txt AIOSEO is the most powerful WordPress SEO plugin. Improve SEO rankings and traffic with comprehensive SEO tools and smart AI SEO optimizations! == Description == ### AIOSEO - The Best WordPress SEO Plugin & Toolkit ### All in One SEO is the original WordPress SEO plugin started in 2007. Today, over 3 million website owners and SEO experts use AIOSEO for higher SEO rankings. Our users consistently rate [AIOSEO](https://aioseo.com/?utm_source=wprepo&utm_medium=link&utm_campaign=liteplugin 'All in One SEO for WordPress') as the most comprehensive WordPress SEO plugin and marketing toolkit. It's the fastest way to optimize WordPress SEO settings, add schema markup, create XML sitemap, add local SEO, track SEO keyword rankings, automate internal linking, perform SEO audits, add Author SEO (EEAT), monitor SEO revisions, connect Google search console, and basically everything a SEO Pro would use to rank higher in search engines. We have AI SEO features that helps you optimize SEO rankings using ChatGPT (OpenAI) and artificial intelligence. > AIOSEO Pro
> This is the lite version of the All in One WordPress SEO Pro plugin that comes with all the powerful SEO features you will ever need to rank higher in search engines including **smart SEO schema markup, advanced SEO modules, powerful SEO sitemap suite, local SEO module, SEO keyword ranking tracking, automatic internal linking, WooCommerce SEO**, and tons more. [Click here to purchase the best premium WordPress SEO plugin now!](https://aioseo.com/?utm_source=wprepo&utm_medium=link&utm_campaign=liteplugin 'All in One SEO for WordPress') Here's why smart business owners, SEO experts, marketers, and developers love AIOSEO, and you will too! [youtube https://youtu.be/UbOYEEIvXvY] ### What Makes AIOSEO Better than Other WordPress SEO Plugins ### AIOSEO is leading the innovation in WordPress SEO space, and our SEO features will give you a competitive advantage. * **Easy SEO Setup Wizard** Our SEO setup wizard optimizes your website's SEO settings based on your unique industry needs in less than 5 minutes. * **Smart Schema Markup (aka Rich Snippets)** Get better click through rate (CTR) and Google rich featured snippets using advanced SEO schema markups like FAQ schema, product schema, recipe schema markup, and dozens more using our custom [Schema Generator](https://aioseo.com/features/rich-snippets-schema/?utm_source=wprepo&utm_medium=link&utm_campaign=liteplugin 'Schema Generator'). * **AI Title and Description Generator** Save time by automatically generating SEO titles and meta descriptions using OpenAI ChatGPT. * **Unlimited SEO Keywords** Optimize for unlimited SEO keywords using our SEO content analyzer. Our TruSEO score gives you detailed content & readability analysis, so you can get higher SEO rankings. * **Google Keyword Rank Tracking** Easily track how your website is ranking for different keywords in Google from your [WordPress dashboard](https://aioseo.com/features/search-statistics/?utm_source=wprepo&utm_medium=link&utm_campaign=liteplugin 'Google Keyword Rank Tracker'). * **Automatic Link Assistant** Automate internal links between your pages using our smart [internal linking algorithm](https://aioseo.com/features/internal-link-assistant/?utm_source=wprepo&utm_medium=link&utm_campaign=liteplugin 'Link Assistant') that will help improve on-page SEO. * **Local Business SEO** Improve your local SEO presence with local business schema, support for multiple local store locations, business opening hours, Google Maps integration, contact info (business email, business phone, business address, etc), and more with our [Local SEO module](https://aioseo.com/features/local-seo/?utm_source=wprepo&utm_medium=link&utm_campaign=liteplugin 'Local SEO'). * **SEO Revisions** Keep a [historical record of SEO changes](https://aioseo.com/seo-revisions/?utm_source=wprepo&utm_medium=link&utm_campaign=liteplugin 'SEO Revisions'), monitor the impact of changes, and restore previous versions in one click. * **Content Decay Tracking** Never lose traffic to competitors. Quickly detect which content is losing traffic / SEO rankings, so you can optimize it to regain your rankings with our [Search Statistics module](https://aioseo.com/features/search-statistics/?utm_source=wprepo&utm_medium=link&utm_campaign=liteplugin 'Search Statistics'). * **Smart XML Sitemap** Advanced XML sitemaps to boost your SEO rankings (with easy setup inside Google Search Console). Also includes Video SEO XML sitemap, News SEO XML sitemap, RSS sitemap, and HTML sitemap. * **Smart SEO Redirects** The most powerful [SEO Redirection manager](https://aioseo.com/features/redirection-manager/?utm_source=wprepo&utm_medium=link&utm_campaign=liteplugin 'Redirection Manager') for setting up advanced SEO redirects including 301 redirects, 302, 307, 410, 404 redirection, REGEX redirects, and more. * **404 Error Monitor** Automatic 404 error monitor helps you track and redirect 404 errors, so you don't lose SEO rankings. * **Author SEO** Add [custom author profile pages, author bio box, and relevant author schema](https://aioseo.com/features/author-seo-google-e-e-a-t/?utm_source=wprepo&utm_medium=link&utm_campaign=liteplugin 'Author SEO (E-E-A-T)') to boost Google EEAT score to help with Google's Helpful Content Update (HCU). * **SEO Audit Checklist** Improve your SEO ranking with our comprehensive SEO audit checklist. * **Knowledge Graph Support** Improve your website's search appearance with SEO Knowledge panel. * **Table of Contents** Automatically generate a table of content, customize headings, anchors, and you can also hide or reorder the headings. ### Advanced SEO Plugin Features ### * **User Access Control** Control who can manage your SEO settings with our advanced SEO access control. * **WordPress REST API** Manage your SEO metadata with WordPress REST API. Great for headless WordPress installations. * **Advanced Robots Meta SEO Settings** Granular controls for no index, no follow, no archive, no snippet, max snippet, max video, etc. * **RSS Content for SEO** Stop content scraping from hurting your SEO rankings. * **Full Site Redirects** Merging websites or switching domains? Full site redirect makes it easy to switch domains without losing SEO rankings. * **Smart Meta Title & Description** Automatic SEO generation, dynamic SEO smart tags, include Emoji, add shortcodes, and more features to stand out in search results. * **Smart Breadcrumbs** Add Breadcrumb navigation to improve user experience and boost your SEO rankings. Comes with full SEO JSON+LD support. * **Automatic Image SEO** Helps your images rank higher by autogenerating image title, clean SEO image filenames, and more. * **Advanced SEO Canonical URLs** Prevent duplicate content in SEO with automatic canonical URLs. * **SEO Cleanup / Manual SEO Penalty Removal** Domains Report feature in Link Assistant automatically removes all links for specific domains with just one click. Huge time saver when doing SEO cleanups. * **Link Opportunities Report** See better internal link opportunities with our smart algorithm. Easily add internal links with just a few clicks. * **Robots.txt Editor** Manage and customize SEO robots.txt files in WordPress. * **Crawl Quota Management** Crawl Cleanup feature manages your search engine crawl quota and index your important content faster. * **Title and Nofollow for SEO** Easily add title and nofollow to external links. * **Headline Analyzer** Analyze your page / posts headlines to improve CTR and SEO rankings. * **Competitor Site SEO Analysis** Use competitor SEO analysis to outrank them with better SEO optimization. * **SEO Code Snippets** Integration with [WPCode plugin](https://wordpress.org/plugins/insert-headers-and-footers/) for SEO code snippets to further customize every aspect of your SEO. ### WordPress SEO Integrations ### * **Google Search Console Integration** Connect with Google webmaster tools and Google Search Console to see SEO insights (like content rankings, keyword rankings, page speed insights, post index status, etc) directly in your WordPress dashboard. * **WooCommerce SEO** Improves your WooCommerce SEO rankings. Easily optimize WooCommerce product pages, product categories, and more for best eCommerce SEO results. * **Knowledge Panel SEO** Improve website SEO appearance by adding social media profile links for Facebook, Twitter, Wikipedia, Instagram, LinkedIn, Yelp, YouTube, and more. * **Webmaster Tool Integrations** Connect with all webmaster tools including Google Search Console, Bing SEO, Yandex SEO, Baidu SEO, Google Analytics, Pinterest site verification, and more. * **Social Media Integration** Facebook SEO, Twitter SEO, and Pinterest SEO with better website previews. * **Google AMP SEO Integration** Improve your mobile SEO rankings with Google AMP SEO. * **Semrush SEO integration** See additional SEO keywords with Semrush SEO integration. * **OpenAI Integration** Use ChatGPT to write SEO titles and meta descriptions to improve SEO rankings. * **Microsoft Clarity Integration** See visitor interactions with heatmaps and session recordings. * **IndexNow Integration** Instantly notify Bing and Yandex for faster SEO indexing. * **Elementor SEO** Better Elementor SEO for landing pages. * **Divi SEO** Better Divi SEO for landing pages. * **SeedProd SEO** Optimize SeedProd landing pages for SEO. * **Open Graph Support** Improve SEO rankings with open graph meta data. ### WordPress SEO Plugin Importer ### Not happy with your current SEO plugin? We make SEO migration easy with our point-and-click automated SEO data transfer tool. We currently support SEO migration from following SEO tools: * Yoast SEO Importer * Yoast SEO Premium Importer * RankMath SEO Importer * SEOPress We also support importing SEO redirects from the following plugins: * Redirection Plugin * Simple 301 Redirects Importer * Safe Redirect Manager * 301 Redirects Importer Aside from that, our SEO migration tool also helps you with: * Import / Export AIOSEO settings from one site to another * Create SEO Settings Backup * CSV Sitemap Import to Import additional pages to your XML Sitemaps **Now you can see why AIOSEO is often rated the best SEO plugin in WordPress.** Give AIOSEO a try. Want to unlock more SEO features? [Upgrade to AIOSEO Pro](https://aioseo.com/?utm_source=wprepo&utm_medium=link&utm_campaign=liteplugin 'All in One SEO for WordPress'). ### Credits ### This plugin is created by [Benjamin Rojas](https://benjaminrojas.net/ 'Benjamin Rojas') and [Syed Balkhi](https://syedbalkhi.com/ 'Syed Balkhi'). ### Branding Guideline ### AIOSEO® is a registered trademark of Semper Plugins LLC. When writing about the WordPress SEO plugin by AIOSEO, please use the following format. * AIOSEO (correct) * All in One SEO (correct) * AIO SEO (incorrect) * All in 1 SEO (incorrect) * AISEO (incorrect) == Changelog == **New in Version 4.8.2** * New: Index Status Report - Quickly identify which posts are indexed by Google and why others aren’t—with granular details on crawl status, fetch results, robots.txt and structured data. * Updated: Improved Table of Contents block rendering performance. * Updated: Hardened limit and order clauses in database queries. * Fixed: WP 6.8 deprecation warning for default Table of Contents and FAQ block styles. * Fixed: Potential page freeze issue when converting Table of Contents block from Group to Columns via block toolbar. * Fixed: Console error in Table of Contents block when switching between Visual and Code Editor in Block Editor. **New in Version 4.8.1.1** * Fixed: CSS style conflict with Woocommerce Memberships and Abandoned Carts. **New in Version 4.8.1** * Updated: DiscussionForumPosting schema is now output for bbPress forum topics and replies as well. * Updated: Improved the SEO Email Report header layout for dates in different languages. * Updated: Expiration date format in AIOSEO General Settings now reflects WordPress date format setting. * Updated: Various Keyword Rank Tracker UI/UX improvements. * Updated: The LinkedIn field in the Setup Wizard is now shown by default. * Updated: "Show post type archive link" setting for products has been removed from their breadcrumb template. * Updated: AIOSEO no longer loads its page builder integration for non-public Thrive Architect post types. * Fixed: Removed support for sitelinks schema as it is no longer supported by Google. * Fixed: YouTube embedded videos with additional parameters in the URL not being detected by the Video Sitemap addon. * Fixed: Uninstalling AIOSEO did not remove user role entries from the database as intended. * Fixed: Limit Modified Date feature sometimes not working for WooCommerce Products. * Fixed: Load admin bar assets on the frontend when logged in only if the admin bar is enabled for that user. * Fixed: Prevent unneeded popup warning on the user profile edit page when leaving without making edits. * Fixed: Restoring SEO revisions now updates meta keys to ensure localization. * Fixed: WPML translation to support admin pages in addition to Author/Reviewer Blocks. * Fixed: Additional keyphrases tooltip cutting off in page builders. * Fixed: Category in the permalink and breadcrumb did not match when the primary category is removed. * Fixed: "See Full Bio" link not correctly displaying for reviewers without posts, even when a custom author bio URL is set. * Fixed: Keyphrase in URL analysis didn't update correctly for auto-draft posts. * Fixed: Search Statistics now disconnects automatically if there's an authentication error. * Fixed: Phrase text missing from the Link Assistant's Link Suggestions table. * Fixed: SERP Preview update when changing primary term for a WooCommerce product. * Fixed: Updated SERP title to reflect selected primary term for the category when changed. * Fixed: SiteOrigin integration style conflict to prevent overlap with other modals. * Fixed: Images using the Post SEO Title smart tag were sometimes not parsed correctly. * Fixed: URL count for the root sitemap sometimes not entirely accurate. * Fixed: Robots.txt settings not correctly synced between multisite network and main site. **New in Version 4.8.0** * New: Crawl Optimization Improvements - We've added even more features to Crawl Cleanup to help you block unwanted bots from indexing your site and prevent spammers from abusing your internal site search results. * Fixed: TruSEO Highlighter not always working correctly when post content contains an empty or reusable block. **New in Version 4.7.9.1** * Updated: Removed the Search Statistics menu item under Dashboard menu. **New in Version 4.7.9** * Updated: Schema product suggestedMinAge/suggestedMaxAge properties to conform with Google's structured data merchant listing requirements. * Updated: Added a button to apply new headlines directly to the post title inside the Headline Analyzer. * Updated: TruSEO now supports additional ACF Custom Field types. * Updated: Exported JSON/CSV files now include the post title and term name columns. * Updated: Improved handling of Focus Keyphrase errors from Semrush API. * Updated: Turned off autoloading by default for a number of AIOSEO options to improve performance. * Fixed: Access control role check to properly verify if the role has edit_post permissions. * Fixed: Existing additional sitemap pages with different priority, frequency, or last modified values were not getting updated when importing a CSV file. * Fixed: Tabs were not displaying active status correctly when using RTL due to CSS conflicts. * Fixed: Image title sometimes duplicated when using Elementor page builder. * Fixed: Alt text settings were not applied to images when showing latest posts on the homepage. * Fixed: Excluded posts/terms would sometimes not be correctly excluded from sitemaps. * Fixed: SERP Preview disappearing when using numeric custom fields in ACF. * Fixed: Filenames duplicated in rare cases when Strip Punctuation is enabled in Image SEO. * Fixed: Adding links to WordPress Gallery Block would sometimes not work when Strip Punctuation is enabled in Image SEO. * Fixed: Trailing slash added to a redirect's target URL even if the URL is external. * Fixed: Orphaned Posts filter not working correctly in the Link Assistant. * Fixed: Table of Content block's heading could sometimes break when adding a link from Link Assistant. * Fixed: "Don't update the modified date" checkbox now works properly in post's quick edit screen. * Fixed: Missing tooltip for the AI Title/Description Generator modal. * Fixed: Headlines that couldn't be analyzed would sometimes break the Headline Analyzer UI. * Fixed: Automatic 404 redirects no longer affecting the dedicated HTML sitemap page. * Fixed: Canonical URL sometimes missing a slash for paged taxonomy terms. * Fixed: Canonical URL for a term could sometimes fail if the global query changed. **New in Version 4.7.8** * New: Keyword Rank Tracker Improvements - Import your best performing keywords directly from Google Search Console and explore Related Keywords to discover new keyword opportunities! * New: WooCommerce Product Attributes Support - Per popular demand, you can now control the SEO of your product attributes and include them in your sitemap to increase discoverability. * Updated: Added filter to control maximum number of additional keywords per post. **See our [changelog on aioseo.com](https://aioseo.com/changelog/?utm_source=wprepo&utm_medium=link&utm_campaign=aioseo) for previous releases.** == Frequently Asked Questions == Please visit our [complete AIOSEO documentation](https://aioseo.com/docs/?utm_source=wprepo&utm_medium=link&utm_campaign=liteplugin) before requesting support for SEO from the AIOSEO team. = Who should use AIOSEO? = SEO is essential for all websites. AIOSEO is perfect for business owners, bloggers, marketers, designers, developers, photographers, and basically everyone else. If you want to rank higher in search, then you need to use AIOSEO WordPress SEO plugin. = Which themes does AIOSEO support? = AIOSEO works with all WordPress themes. Simply enable AIOSEO to make your WordPress theme SEO friendly. = Will AIOSEO slow down my website? = Nope, AIOSEO will NOT slow down your website. We understand that speed is important for SEO, that's why our code is properly optimized for maximum performance. Remember, faster websites rank higher in search. Use AIOSEO for fast SEO improvements. = Can I use AIOSEO on client sites? = Yes, you can use AIOSEO on client websites. = Are AIOSEO sitemaps better than default WordPress sitemaps? = Yes, AIOSEO smart sitemaps are a lot more optimized than the default WordPress sitemaps. Once you enable AIOSEO, our XML sitemaps will override the default WordPress sitemaps, so you can improve your SEO rankings. We also offer advanced SEO sitemaps such as News Sitemap, Video Sitemap, and RSS Sitemap. Our SEO sitemaps come with granular control such as links per sitemap, enable / disable post types or taxonomies, include / exclude specific links from sitemap, add additional non-WordPress pages to sitemaps, customize sitemap priority & frequency for each section of your site, and more. This is why experts rate AIOSEO as the best WordPress SEO plugin. = Does AIOSEO help with SEO Verification? = Yes. AIOSEO can help you with website SEO verification with various webmaster tools such as Google Search Console, Bing Webmaster Tools, Yandex, Baidu, Pinterest, and just about every other site verification you need. = Why is AIOSEO better than other SEO plugins? = There are many WordPress SEO plugins out there. Unlike others, AIOSEO WordPress SEO plugin is always reliable. Our SEO features are results focused (no bloat), and we offer exceptional customer support. AIOSEO is the original WordPress SEO plugin, and it's trusted by over 3 million website owners. = Do I really need an XML Sitemap? = **Yes! XML Sitemaps help Google and other search engines to find all the pages of your website.** An XML sitemap is a list of all the content on your website. The sitemap helps search engine bots to easily see all the content on your site in one place, The XML sitemap file is hidden from your human visitors, however search engines like Google can see it. Without an XML sitemap, some of your web pages may never be included in Google search results, and won't get any traffic. XML Sitemaps also help you tell Google which pages you DON'T want included in search results. This can help your SEO to prevent keyword cannibalization and duplicate content issues. As part of your SEO strategy, **an XML sitemap can help you to improve your domain authority and unlock more traffic from Google, Bing and other search engines**. AIOSEO can easily help you get your sitemaps listed inside Google Search Console so your content can start to get indexed today! = Does AIOSEO integrate directly with Google Search Console? = Absolutely! Our integration with Google Search Console allows you to monitor and maintain your website's presence on Google. With our direct integration, you can easily view important information about your website, such as the number of clicks, impressions, and the average position for each keyword that your website's content appears for in Google search results. You can also track your contents page speed using Google's Page Speed Insights directly inside your WordPress dashboard. Additionally, AIOSEO can also provide you with data on the most frequently used keywords, the most popular pages on your website, and any crawl errors or security issues that may arise. By integrating with Google Search Console, AIOSEO provides website owners with valuable insights that can help to improve SEO and overall online visibility. With this integration, you can track your site's progress over time and make data-driven decisions that will help you achieve your SEO goals. == Screenshots == 1. SEO Content Analyzer (Gutenberg) 2. SEO Content Analyzer (Classic Editor) 3. SEO Setup Wizard 4. SEO Site Analysis 5. Webmaster Tools Connect 6. Social Media Integrations 7. Local SEO 8. Sitemaps 9. Search Appearance Settings 10. Robots.txt Editor 11. RSS Content Control 12. Headline Analyzer 13. Redirect Manager 14. Link Assistant == Upgrade Notice == = 4.8.2 = This update adds major improvements and bug fixes.all_in_one_seo_pack.php000064400000006710150211766350011232 0ustar00. * * @since 4.0.0 * @author All in One SEO Team * @package AIOSEO\Plugin * @license GPL-3.0+ * @copyright Copyright © 2025, All in One SEO */ // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } require_once dirname( __FILE__ ) . '/app/init/init.php'; // Check if this plugin should be disabled. if ( aioseoMaybePluginIsDisabled( __FILE__ ) ) { return; } if ( ! defined( 'AIOSEO_PHP_VERSION_DIR' ) ) { define( 'AIOSEO_PHP_VERSION_DIR', basename( dirname( __FILE__ ) ) ); } require_once dirname( __FILE__ ) . '/app/init/notices.php'; require_once dirname( __FILE__ ) . '/app/init/activation.php'; // We require PHP 7.0 or higher for the whole plugin to work. if ( version_compare( PHP_VERSION, '7.0', '<' ) ) { add_action( 'admin_notices', 'aioseo_php_notice' ); // Do not process the plugin code further. return; } // We require WordPress 5.3+ for the whole plugin to work. // Support for 5.3 is scheduled to be dropped in April 2025. 5.4, 5.5 and 5.6 will be dropped at the end of 2025. global $wp_version; // phpcs:ignore Squiz.NamingConventions.ValidVariableName if ( version_compare( $wp_version, '5.3', '<' ) ) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName add_action( 'admin_notices', 'aioseo_wordpress_notice' ); // Do not process the plugin code further. return; } if ( ! defined( 'AIOSEO_DIR' ) ) { define( 'AIOSEO_DIR', __DIR__ ); } if ( ! defined( 'AIOSEO_FILE' ) ) { define( 'AIOSEO_FILE', __FILE__ ); } // Don't allow multiple versions to be active. if ( function_exists( 'aioseo' ) ) { add_action( 'activate_all-in-one-seo-pack/all_in_one_seo_pack.php', 'aioseo_lite_just_activated' ); add_action( 'deactivate_all-in-one-seo-pack/all_in_one_seo_pack.php', 'aioseo_lite_just_deactivated' ); add_action( 'activate_all-in-one-seo-pack-pro/all_in_one_seo_pack.php', 'aioseo_pro_just_activated' ); add_action( 'admin_notices', 'aioseo_lite_notice' ); // Do not process the plugin code further. return; } // We will be deprecating these versions of PHP in the future, so we'll let the user know. if ( version_compare( PHP_VERSION, '7.4', '<' ) ) { add_action( 'admin_notices', 'aioseo_php_notice_deprecated' ); } // Define the class and the function. // The AIOSEOAbstract class is required here because it can't be autoloaded. require_once dirname( __FILE__ ) . '/app/AIOSEOAbstract.php'; require_once dirname( __FILE__ ) . '/app/AIOSEO.php'; aioseo();app/Lite/Traits/Options.php000064400000005230150211766350011641 0ustar00getDbOptions( $this->optionsName . '_lite' ); // Refactor options. $this->defaultsMerged = array_replace_recursive( $this->defaults, $this->liteDefaults ); $mergedDefaults = array_replace_recursive( $this->liteDefaults, $this->addValueToValuesArray( $this->liteDefaults, $dbOptions ) ); $cachedOptions = aioseo()->core->optionsCache->getOptions( $this->optionsName ); $dbOptions = array_replace_recursive( $cachedOptions, $mergedDefaults ); aioseo()->core->optionsCache->setOptions( $this->optionsName, $dbOptions ); } /** * Merge defaults with liteDefaults. * * @since 4.1.4 * * @return array An array of dafults. */ public function getDefaults() { return array_replace_recursive( parent::getDefaults(), $this->liteDefaults ); } /** * Updates the options in the database. * * @since 4.1.4 * * @param string $optionsName An optional option name to update. * @param string $defaults The defaults to filter the options by. * @param array|null $options An optional options array. * @return void */ public function update( $optionsName = null, $defaults = null, $options = null ) { $optionsName = empty( $optionsName ) ? $this->optionsName . '_lite' : $optionsName; $defaults = empty( $defaults ) ? $this->liteDefaults : $defaults; // We're creating a new array here because it was setting it by reference. $cachedOptions = aioseo()->core->optionsCache->getOptions( $this->optionsName ); $optionsBefore = json_decode( wp_json_encode( $cachedOptions ), true ); parent::update( $this->optionsName, $options ); parent::update( $optionsName, $defaults, $optionsBefore ); } /** * Updates the options in the database. * * @since 4.1.4 * * @param boolean $force Whether or not to force an immediate save. * @param string $optionsName An optional option name to update. * @param string $defaults The defaults to filter the options by. * @return void */ public function save( $force = false, $optionsName = null, $defaults = null ) { if ( ! $this->shouldSave && ! $force ) { return; } $optionsName = empty( $optionsName ) ? $this->optionsName . '_lite' : $optionsName; $defaults = empty( $defaults ) ? $this->liteDefaults : $defaults; parent::save( $force, $this->optionsName ); parent::save( $force, $optionsName, $defaults ); } }app/Lite/Utils/Helpers.php000064400000001314150211766350011441 0ustar00routes, $this->liteRoutes ); } }app/Lite/Api/Wizard.php000064400000002124150211766350010710 0ustar00get_json_params(); $section = ! empty( $body['section'] ) ? sanitize_text_field( $body['section'] ) : null; $wizard = ! empty( $body['wizard'] ) ? $body['wizard'] : null; // Save the smart recommendations section. if ( 'smartRecommendations' === $section && ! empty( $wizard['smartRecommendations'] ) ) { $smartRecommendations = $wizard['smartRecommendations']; if ( isset( $smartRecommendations['usageTracking'] ) ) { aioseo()->options->advanced->usageTracking = $smartRecommendations['usageTracking']; } } return $response; } }app/Lite/Options/InternalOptions.php000064400000002071150211766350013523 0ustar00 [ 'activated' => [ 'type' => 'number', 'default' => 0 ], 'firstActivated' => [ 'type' => 'number', 'default' => 0 ], 'installed' => [ 'type' => 'number', 'default' => 0 ], 'connect' => [ 'key' => [ 'type' => 'string' ], 'time' => [ 'type' => 'number', 'default' => 0 ], 'network' => [ 'type' => 'boolean', 'default' => false ], 'token' => [ 'type' => 'string' ] ] ] // phpcs:enable WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound ]; }app/Lite/Options/Options.php000064400000002371150211766350012031 0ustar00 [ 'usageTracking' => [ 'type' => 'boolean', 'default' => false ] ] // phpcs:enable WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound ]; /** * Sanitizes, then saves the options to the database. * * @since 4.7.2 * * @param array $options An array of options to sanitize, then save. * @return void */ public function sanitizeAndSave( $options ) { if ( isset( $options['advanced']['emailSummary']['recipients'] ) ) { $options['advanced']['emailSummary']['recipients'] = [ array_shift( $options['advanced']['emailSummary']['recipients'] ) ]; $options['advanced']['emailSummary']['recipients'][0]['frequency'] = 'monthly'; } parent::sanitizeAndSave( $options ); } }app/Lite/Main/Filters.php000064400000005774150211766350011251 0ustar00', 5 ); $actionLinks = [ 'suggest-feature' => [ // Translators: This is an action link users can click to open a feature request. 'label' => __( 'Suggest a Feature', 'all-in-one-seo-pack' ), 'url' => aioseo()->helpers->utmUrl( AIOSEO_MARKETING_URL . 'suggest-a-feature/', 'plugin-row-meta', 'feature' ), ], 'review' => [ 'label' => $reviewLabel, 'url' => aioseo()->helpers->utmUrl( AIOSEO_MARKETING_URL . 'review-aioseo', 'plugin-row-meta', 'review' ), 'title' => sprintf( // Translators: 1 - The plugin short name ("AIOSEO"). __( 'Rate %1$s', 'all-in-one-seo-pack' ), 'AIOSEO' ) ] ]; return $this->parseActionLinks( $actions, $pluginFile, $actionLinks ); } /** * Registers our action links for the plugins page. * * @since 4.0.0 * * @param array $actions List of existing actions. * @param string $pluginFile The plugin file. * @return array List of action links. */ public function pluginActionLinks( $actions, $pluginFile = '' ) { $actionLinks = [ 'settings' => [ 'label' => __( 'SEO Settings', 'all-in-one-seo-pack' ), 'url' => get_admin_url( null, 'admin.php?page=aioseo-settings' ), ], 'support' => [ // Translators: This is an action link users can click to open our premium support. 'label' => __( 'Support', 'all-in-one-seo-pack' ), 'url' => aioseo()->helpers->utmUrl( AIOSEO_MARKETING_URL . 'contact/', 'plugin-action-links', 'Support' ), ], 'docs' => [ // Translators: This is an action link users can click to open our general documentation page. 'label' => __( 'Documentation', 'all-in-one-seo-pack' ), 'url' => aioseo()->helpers->utmUrl( AIOSEO_MARKETING_URL . 'docs/', 'plugin-action-links', 'Documentation' ), ], 'proupgrade' => [ // Translators: This is an action link users can click to purchase a license for All in One SEO Pro. 'label' => __( 'Upgrade to Pro', 'all-in-one-seo-pack' ), 'url' => apply_filters( 'aioseo_upgrade_link', aioseo()->helpers->utmUrl( AIOSEO_MARKETING_URL . 'lite-upgrade/', 'plugin-action-links', 'Upgrade', false ) ), ] ]; if ( isset( $actions['edit'] ) ) { unset( $actions['edit'] ); } return $this->parseActionLinks( $actions, $pluginFile, $actionLinks, 'before' ); } }app/Lite/Views/taxonomy-upsell.php000064400000133476150211766350013233 0ustar00

General
Social
Redirects
SEO Revisions
Advanced
Snippet Preview
https://aioseo.com/category/uncategorized/
Taxonomy Title | aioseo.com
Sample taxonomy description
Category Title
Click on the tags below to insert variables into your title.
Category Title
Separator
Site Title
View all tags →
Category Description
Category Description
Current or first category description.
Category Title
Category Title
Current or first category title.
Current Date
Current Date
The current date, localized.
Current Day
Current Day
The current day of the month, localized.
Current Month
Current Month
The current month, localized.
Current Year
Current Year
The current year, localized.
Custom Field
Custom Field
A custom field from the current page/post.
Permalink
Permalink
The permalink for the current page/post.
Separator
Separator
The separator defined in the search appearance settings.
Site Title
Site Title
Your site title.
Tagline
Tagline
The tagline for your site, set in the general settings.
Meta Description
Click on the tags below to insert variables into your meta description.
Category Title
Separator
Category Description
View all tags →
Category Description
Category Description
Current or first category description.
Category Title
Category Title
Current or first category title.
Current Date
Current Date
The current date, localized.
Current Day
Current Day
The current day of the month, localized.
Current Month
Current Month
The current month, localized.
Current Year
Current Year
The current year, localized.
Custom Field
Custom Field
A custom field from the current page/post.
Permalink
Permalink
The permalink for the current page/post.
Separator
Separator
The separator defined in the search appearance settings.
Site Title
Site Title
Your site title.
Tagline
Tagline
The tagline for your site, set in the general settings.
SEO Title/Description
Social Meta
SEO Revisions
Import/Export
🎁 .
app/Lite/Admin/PostSettings.php000064400000003366150211766350012446 0ustar00label ) || ! is_taxonomy_viewable( $taxObject ) ) { unset( $taxonomies[ $taxObject->name ] ); } } foreach ( $taxonomies as $taxonomy ) { add_action( $taxonomy->name . '_edit_form', [ $this, 'addTaxonomyUpsell' ] ); add_action( 'after-' . $taxonomy->name . '-table', [ $this, 'addTaxonomyUpsell' ] ); } } } /** * Add Taxonomy Upsell * * @since 4.0.0 * * @return void */ public function addTaxonomyUpsell() { $screen = aioseo()->helpers->getCurrentScreen(); if ( ! isset( $screen->parent_base ) || 'edit' !== $screen->parent_base || empty( $screen->taxonomy ) ) { return; } include_once AIOSEO_DIR . '/app/Lite/Views/taxonomy-upsell.php'; } }app/Lite/Admin/Connect.php000064400000027046150211766350011372 0ustar00loadConnectPro(); return; } $this->loadConnect(); // phpcs:enable } /** * Load the Connect template. * * @since 4.0.0 * * @return void */ private function loadConnect() { $this->enqueueScripts(); $this->connectHeader(); $this->connectContent(); $this->connectFooter(); exit; } /** * Load the Connect Pro template. * * @since 4.0.0 * * @return void */ private function loadConnectPro() { $this->enqueueScriptsPro(); $this->connectHeader(); $this->connectContent(); $this->connectFooter( 'pro' ); exit; } /** * Enqueue's scripts for the setup wizard. * * @since 4.0.0 * * @return void */ public function enqueueScripts() { // We don't want any plugin adding notices to our screens. Let's clear them out here. remove_all_actions( 'admin_notices' ); remove_all_actions( 'network_admin_notices' ); remove_all_actions( 'all_admin_notices' ); aioseo()->core->assets->load( 'src/vue/standalone/connect/main.js', [], aioseo()->helpers->getVueData() ); } /** * Enqueue's scripts for the setup wizard. * * @since 4.0.0 * * @return void */ public function enqueueScriptsPro() { // We don't want any plugin adding notices to our screens. Let's clear them out here. remove_all_actions( 'admin_notices' ); remove_all_actions( 'network_admin_notices' ); remove_all_actions( 'all_admin_notices' ); aioseo()->core->assets->load( 'src/vue/standalone/connect-pro/main.js', [], aioseo()->helpers->getVueData() ); } /** * Outputs the simplified header used for the Onboarding Wizard. * * @since 4.0.0 * * @return void */ public function connectHeader() { ?> > <?php // Translators: 1 - The plugin name ("All in One SEO"). echo sprintf( esc_html__( '%1$s › Connect', 'all-in-one-seo-pack' ), esc_html( AIOSEO_PLUGIN_NAME ) ); ?> '; aioseo()->templates->getTemplate( 'admin/settings-page.php' ); echo ''; } /** * Outputs the simplified footer used for the Onboarding Wizard. * * @since 4.0.0 * * @return void */ public function connectFooter( $pro = '' ) { ?> esc_html__( 'You are not allowed to install plugins.', 'all-in-one-seo-pack' ) ]; } if ( empty( $key ) ) { return [ 'error' => esc_html__( 'Please enter your license key to connect.', 'all-in-one-seo-pack' ), ]; } // Verify pro version is not installed. $active = activate_plugin( 'all-in-one-seo-pack-pro/all_in_one_seo_pack_pro', false, false, true ); if ( ! is_wp_error( $active ) ) { return [ 'error' => esc_html__( 'Pro version is already installed.', 'all-in-one-seo-pack' ) ]; } // Just check if network is set. $network = isset( $_POST['network'] ) ? (bool) sanitize_text_field( wp_unslash( $_POST['network'] ) ) : false; // phpcs:ignore HM.Security.ValidatedSanitizedInput.InputNotSanitized, HM.Security.NonceVerification.Missing, WordPress.Security.NonceVerification, Generic.Files.LineLength.MaxExceeded $network = ! empty( $network ); // Generate a hash that can be compared after the user is redirected back. $oth = hash( 'sha512', wp_rand() ); $hashedOth = hash_hmac( 'sha512', $oth, wp_salt() ); // Save the options. aioseo()->internalOptions->internal->connect->key = $key; aioseo()->internalOptions->internal->connect->time = time(); aioseo()->internalOptions->internal->connect->network = $network; aioseo()->internalOptions->internal->connect->token = $oth; $url = add_query_arg( [ 'key' => $key, 'network' => $network, 'token' => $hashedOth, 'version' => aioseo()->version, 'siteurl' => admin_url(), 'homeurl' => home_url(), 'endpoint' => admin_url( 'admin-ajax.php' ), 'php' => PHP_VERSION, 'wp' => get_bloginfo( 'version' ), 'redirect' => rawurldecode( base64_encode( $redirect ? $redirect : admin_url( 'admin.php?page=aioseo-settings' ) ) ), 'v' => 1, ], defined( 'AIOSEO_UPGRADE_URL' ) ? AIOSEO_UPGRADE_URL : 'https://upgrade.aioseo.com' ); // We're storing the ID of the user who is installing Pro so that we can add capabilties for him after upgrading. aioseo()->core->cache->update( 'connect_active_user', get_current_user_id(), 15 * MINUTE_IN_SECONDS ); return [ 'url' => $url, ]; } /** * Process AIOSEO Connect. * * @since 1.0.0 * * @return array An array containing a valid response or an error message. */ public function process() { // phpcs:disable HM.Security.NonceVerification.Missing, WordPress.Security.NonceVerification $hashedOth = ! empty( $_POST['token'] ) ? sanitize_text_field( wp_unslash( $_POST['token'] ) ) : ''; $downloadUrl = ! empty( $_POST['file'] ) ? esc_url_raw( wp_unslash( $_POST['file'] ) ) : ''; // phpcs:enable $error = sprintf( // Translators: 1 - The marketing site domain ("aioseo.com"). esc_html__( 'Could not install upgrade. Please download from %1$s and install manually.', 'all-in-one-seo-pack' ), esc_html( AIOSEO_MARKETING_DOMAIN ) ); $success = esc_html__( 'Plugin installed & activated.', 'all-in-one-seo-pack' ); // Check if all required params are present. if ( empty( $downloadUrl ) || empty( $hashedOth ) ) { wp_send_json_error( $error ); } $oth = aioseo()->internalOptions->internal->connect->token; if ( empty( $oth ) ) { wp_send_json_error( $error ); } // Check if the stored hash matches the salted one that is sent back from the server. if ( hash_hmac( 'sha512', $oth, wp_salt() ) !== $hashedOth ) { wp_send_json_error( $error ); } // Delete connect token so we don't replay. aioseo()->internalOptions->internal->connect->token = null; // Verify pro not activated. if ( aioseo()->pro ) { wp_send_json_success( $success ); } // Check license key. $licenseKey = aioseo()->internalOptions->internal->connect->key; if ( ! $licenseKey ) { wp_send_json_error( esc_html__( 'You are not licensed.', 'all-in-one-seo-pack' ) ); } // Set the license key in a new option so we can get it when Pro is activated. aioseo()->internalOptions->internal->validLicenseKey = $licenseKey; require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-screen.php'; require_once ABSPATH . 'wp-admin/includes/screen.php'; // Set the current screen to avoid undefined notices. set_current_screen( 'toplevel_page_aioseo' ); // Prepare variables. $url = esc_url_raw( add_query_arg( [ 'page' => 'aioseo-settings', ], admin_url( 'admin.php' ) ) ); // Verify pro not installed. $network = aioseo()->internalOptions->internal->connect->network; $active = activate_plugin( 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php', $url, $network, true ); if ( ! is_wp_error( $active ) ) { aioseo()->internalOptions->internal->connect->reset(); // Because the regular activation hooks won't run, we need to add capabilities for the installing user so that he doesn't run into an error on the first request. aioseo()->activate->addCapabilitiesOnUpgrade(); wp_send_json_success( $success ); } $creds = request_filesystem_credentials( $url, '', false, false, null ); // Check for file system permissions. if ( false === $creds ) { wp_send_json_error( $error ); } $fs = aioseo()->core->fs->noConflict(); $fs->init( $creds ); if ( ! $fs->isWpfsValid() ) { wp_send_json_error( $error ); } // Do not allow WordPress to search/download translations, as this will break JS output. remove_action( 'upgrader_process_complete', [ 'Language_Pack_Upgrader', 'async_upgrade' ], 20 ); // Create the plugin upgrader with our custom skin. $installer = new Utils\PluginUpgraderSilentAjax( new Utils\PluginUpgraderSkin() ); // Error check. if ( ! method_exists( $installer, 'install' ) ) { wp_send_json_error( $error ); } $installer->install( $downloadUrl ); // Flush the cache and return the newly installed plugin basename. wp_cache_flush(); $pluginBasename = $installer->plugin_info(); if ( ! $pluginBasename ) { wp_send_json_error( $error ); } // Activate the plugin silently. $activated = activate_plugin( $pluginBasename, '', $network, true ); if ( is_wp_error( $activated ) ) { wp_send_json_error( esc_html__( 'The Pro version installed correctly, but it needs to be activated from the Plugins page inside your WordPress admin.', 'all-in-one-seo-pack' ) ); } aioseo()->internalOptions->internal->connect->reset(); // Because the regular activation hooks won't run, we need to add capabilities for the installing user so that he doesn't run into an error on the first request. aioseo()->activate->addCapabilitiesOnUpgrade(); wp_send_json_success( $success ); } }app/Lite/Admin/Notices/Notices.php000064400000004706150211766350013007 0ustar00wooUpsellNotice(); } /** * Validates the notification type. * * @since 4.0.0 * * @param string $type The notification type we are targeting. * @return boolean True if yes, false if no. */ public function validateType( $type ) { $validated = parent::validateType( $type ); // Any lite notification should pass here. if ( 'lite' === $type ) { $validated = true; } return $validated; } /** * Add a notice if WooCommerce is detected and not licensed or running Lite. * * @since 4.0.0 * * @return void */ private function wooUpsellNotice() { $notification = Models\Notification::getNotificationByName( 'woo-upsell' ); if ( ! class_exists( 'WooCommerce' ) ) { if ( $notification->exists() ) { Models\Notification::deleteNotificationByName( 'woo-upsell' ); } return; } if ( $notification->exists() ) { return; } Models\Notification::addNotification( [ 'slug' => uniqid(), 'notification_name' => 'woo-upsell', // Translators: 1 - "WooCommerce". 'title' => sprintf( __( 'Advanced %1$s Support', 'all-in-one-seo-pack' ), 'WooCommerce' ), // Translators: 1 - "WooCommerce", 2 - The plugin short name ("AIOSEO"). 'content' => sprintf( __( 'We have detected you are running %1$s. Upgrade to %2$s to unlock our advanced eCommerce SEO features, including SEO for Product Categories and more.', 'all-in-one-seo-pack' ), 'WooCommerce', AIOSEO_PLUGIN_SHORT_NAME . ' Pro' ), // phpcs:ignore Generic.Files.LineLength.MaxExceeded 'type' => 'info', 'level' => [ 'all' ], // Translators: 1 - "Pro". 'button1_label' => sprintf( __( 'Upgrade to %1$s', 'all-in-one-seo-pack' ), 'Pro' ), 'button1_action' => html_entity_decode( apply_filters( 'aioseo_upgrade_link', aioseo()->helpers->utmUrl( AIOSEO_MARKETING_URL . 'lite-upgrade/', 'woo-notification-upsell', false ) ) ), 'start' => gmdate( 'Y-m-d H:i:s' ) ] ); } }app/Lite/Admin/Usage.php000064400000001116150211766350011033 0ustar00enabled = aioseo()->options->advanced->usageTracking; } /** * Get the type for the request. * * @since 4.0.0 * * @return string The install type. */ public function getType() { return 'lite'; } }app/Lite/Admin/Admin.php000064400000005223150211766350011022 0ustar00connect = new Connect(); } /** * Actually adds the menu items to the admin bar. * * @since 4.0.0 * * @return void */ protected function addAdminBarMenuItems() { // Add an upsell to Pro. if ( current_user_can( $this->getPageRequiredCapability( '' ) ) ) { $this->adminBarMenuItems['aioseo-pro-upgrade'] = [ 'parent' => 'aioseo-main', 'title' => '' . __( 'Upgrade to Pro', 'all-in-one-seo-pack' ) . '', 'id' => 'aioseo-pro-upgrade', 'href' => apply_filters( 'aioseo_upgrade_link', esc_url( admin_url( 'admin.php?page=aioseo-tools&aioseo-redirect-upgrade=1' ) ) ), 'meta' => [ 'target' => '_blank' ], ]; } parent::addAdminBarMenuItems(); } /** * Add the menu inside of WordPress. * * @since 4.0.0 * * @return void */ public function addMenu() { parent::addMenu(); $capability = $this->getPageRequiredCapability( '' ); // We use the global submenu, because we are adding an external link here. if ( current_user_can( $capability ) ) { global $submenu; $submenu[ $this->pageSlug ][] = [ '' . esc_html__( 'Upgrade to Pro', 'all-in-one-seo-pack' ) . '', $capability, apply_filters( 'aioseo_upgrade_link', esc_url( admin_url( 'admin.php?page=aioseo-tools&aioseo-redirect-upgrade=1' ) ) ) ]; } } /** * Check the query args to see if we need to redirect to an external URL. * * @since 4.2.3 * * @return void */ protected function checkForRedirects() { $mappedUrls = [ // Added to resolve an issue with the open_basedir in the IIS. // https://github.com/awesomemotive/aioseo/issues/3243 'aioseo-redirect-upgrade' => apply_filters( 'aioseo_upgrade_link', aioseo()->helpers->utmUrl( AIOSEO_MARKETING_URL . 'lite-upgrade/', 'admin-bar', null, false ) ) ]; foreach ( $mappedUrls as $queryArg => $redirectUrl ) { if ( isset( $_GET[ $queryArg ] ) ) { // phpcs:ignore HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended wp_redirect( $redirectUrl ); } } } }app/AIOSEO.php000064400000031211150211766350007020 0ustar00init(); // Load our addons on the action right after plugins_loaded. add_action( 'sanitize_comment_cookies', [ self::$instance, 'loadAddons' ] ); } return self::$instance; } /** * Initialize All in One SEO! * * @since 4.0.0 * * @return void */ private function init() { $this->constants(); $this->includes(); $this->preLoad(); if ( ! $this->core->isUninstalling() ) { $this->load(); } } /** * Setup plugin constants. * All the path/URL related constants are defined in main plugin file. * * @since 4.0.0 * * @return void */ private function constants() { $defaultHeaders = [ 'name' => 'Plugin Name', 'version' => 'Version', ]; $pluginData = get_file_data( AIOSEO_FILE, $defaultHeaders ); $constants = [ 'AIOSEO_PLUGIN_BASENAME' => plugin_basename( AIOSEO_FILE ), 'AIOSEO_PLUGIN_NAME' => $pluginData['name'], 'AIOSEO_PLUGIN_SHORT_NAME' => 'AIOSEO', 'AIOSEO_PLUGIN_URL' => plugin_dir_url( AIOSEO_FILE ), 'AIOSEO_VERSION' => $pluginData['version'], 'AIOSEO_MARKETING_URL' => 'https://aioseo.com/', 'AIOSEO_MARKETING_DOMAIN' => 'aioseo.com' ]; foreach ( $constants as $constant => $value ) { if ( ! defined( $constant ) ) { define( $constant, $value ); } } $this->version = AIOSEO_VERSION; } /** * Including the new files with PHP 5.3 style. * * @since 4.0.0 * * @return void */ private function includes() { $dependencies = [ '/vendor/autoload.php' => true, '/vendor/woocommerce/action-scheduler/action-scheduler.php' => true, '/vendor/jwhennessey/phpinsight/autoload.php' => false, '/vendor_prefixed/monolog/monolog/src/Monolog/Logger.php' => false ]; foreach ( $dependencies as $path => $shouldRequire ) { if ( ! file_exists( AIOSEO_DIR . $path ) ) { // Something is not right. status_header( 500 ); wp_die( esc_html__( 'Plugin is missing required dependencies. Please contact support for more information.', 'all-in-one-seo-pack' ) ); } if ( $shouldRequire ) { require_once AIOSEO_DIR . $path; } } $this->loadVersion(); } /** * Load the version of the plugin we are currently using. * * @since 4.1.9 * * @return void */ private function loadVersion() { $proDir = is_dir( plugin_dir_path( AIOSEO_FILE ) . 'app/Pro' ); if ( ! class_exists( '\Dotenv\Dotenv' ) || ! file_exists( AIOSEO_DIR . '/build/.env' ) ) { $this->pro = $proDir; $this->versionPath = $proDir ? 'Pro' : 'Lite'; return; } $dotenv = \Dotenv\Dotenv::createUnsafeImmutable( AIOSEO_DIR, '/build/.env' ); $dotenv->load(); $version = defined( 'AIOSEO_DEV_VERSION' ) ? strtolower( AIOSEO_DEV_VERSION ) : strtolower( getenv( 'VITE_VERSION' ) ); if ( ! empty( $version ) ) { $this->isDev = true; if ( file_exists( AIOSEO_DIR . '/build/filters.php' ) ) { require_once AIOSEO_DIR . '/build/filters.php'; } } if ( $proDir && 'pro' === $version ) { $this->pro = true; $this->versionPath = 'Pro'; } } /** * Runs before we load the plugin. * * @since 4.0.0 * * @return void */ private function preLoad() { $this->core = new Common\Core\Core(); $this->backwardsCompatibility(); // Internal Options. $this->helpers = $this->pro ? new Pro\Utils\Helpers() : new Lite\Utils\Helpers(); $this->internalNetworkOptions = ( $this->pro && $this->helpers->isPluginNetworkActivated() ) ? new Pro\Options\InternalNetworkOptions() : new Common\Options\InternalNetworkOptions(); $this->internalOptions = $this->pro ? new Pro\Options\InternalOptions() : new Lite\Options\InternalOptions(); $this->uninstall = new Common\Main\Uninstall(); // Run pre-updates. $this->preUpdates = $this->pro ? new Pro\Main\PreUpdates() : new Common\Main\PreUpdates(); } /** * To prevent errors and bugs from popping up, * we will run this backwards compatibility method. * * @since 4.1.9 * * @return void */ private function backwardsCompatibility() { $this->db = $this->core->db; $this->cache = $this->core->cache; $this->transients = $this->cache; $this->cachePrune = $this->core->cachePrune; $this->optionsCache = $this->core->optionsCache; } /** * To prevent errors and bugs from popping up, * we will run this backwards compatibility method. * * @since 4.2.0 * * @return void */ private function backwardsCompatibilityLoad() { $this->postSettings->integrations = $this->standalone->pageBuilderIntegrations; } /** * Load our classes. * * @since 4.0.0 * * @return void */ public function load() { // Load external translations if this is a Pro install. if ( $this->pro ) { $translations = new Pro\Main\Translations( 'plugin', 'all-in-one-seo-pack', 'https://aioseo.com/aioseo-plugin/all-in-one-seo-pack/packages.json' ); $translations->init(); $translations = new Pro\Main\Translations( 'plugin', 'aioseo-pro', 'https://aioseo.com/aioseo-plugin/aioseo-pro/packages.json' ); $translations->init(); } $this->addons = $this->pro ? new Pro\Utils\Addons() : new Common\Utils\Addons(); $this->features = $this->pro ? new Pro\Utils\Features() : new Common\Utils\Features(); $this->tags = $this->pro ? new Pro\Utils\Tags() : new Common\Utils\Tags(); $this->blocks = new Common\Utils\Blocks(); $this->badBotBlocker = new Common\Tools\BadBotBlocker(); $this->breadcrumbs = $this->pro ? new Pro\Breadcrumbs\Breadcrumbs() : new Common\Breadcrumbs\Breadcrumbs(); $this->dynamicBackup = $this->pro ? new Pro\Options\DynamicBackup() : new Common\Options\DynamicBackup(); $this->options = $this->pro ? new Pro\Options\Options() : new Lite\Options\Options(); $this->networkOptions = ( $this->pro && $this->helpers->isPluginNetworkActivated() ) ? new Pro\Options\NetworkOptions() : new Common\Options\NetworkOptions(); $this->dynamicOptions = $this->pro ? new Pro\Options\DynamicOptions() : new Common\Options\DynamicOptions(); $this->backup = new Common\Utils\Backup(); $this->access = $this->pro ? new Pro\Utils\Access() : new Common\Utils\Access(); $this->usage = $this->pro ? new Pro\Admin\Usage() : new Lite\Admin\Usage(); $this->siteHealth = $this->pro ? new Pro\Admin\SiteHealth() : new Common\Admin\SiteHealth(); $this->networkLicense = $this->pro && $this->helpers->isPluginNetworkActivated() ? new Pro\Admin\NetworkLicense() : null; $this->license = $this->pro ? new Pro\Admin\License() : null; $this->autoUpdates = $this->pro ? new Pro\Admin\AutoUpdates() : null; $this->updates = $this->pro ? new Pro\Main\Updates() : new Common\Main\Updates(); $this->meta = $this->pro ? new Pro\Meta\Meta() : new Common\Meta\Meta(); $this->social = $this->pro ? new Pro\Social\Social() : new Common\Social\Social(); $this->robotsTxt = new Common\Tools\RobotsTxt(); $this->htaccess = new Common\Tools\Htaccess(); $this->term = $this->pro ? new Pro\Admin\Term() : null; $this->notices = $this->pro ? new Pro\Admin\Notices\Notices() : new Lite\Admin\Notices\Notices(); $this->wpNotices = new Common\Admin\Notices\WpNotices(); $this->admin = $this->pro ? new Pro\Admin\Admin() : new Lite\Admin\Admin(); $this->networkAdmin = $this->helpers->isPluginNetworkActivated() ? ( $this->pro ? new Pro\Admin\NetworkAdmin() : new Common\Admin\NetworkAdmin() ) : null; $this->activate = $this->pro ? new Pro\Main\Activate() : new Common\Main\Activate(); $this->conflictingPlugins = $this->pro ? new Pro\Admin\ConflictingPlugins() : new Common\Admin\ConflictingPlugins(); $this->migration = $this->pro ? new Pro\Migration\Migration() : new Common\Migration\Migration(); $this->importExport = $this->pro ? new Pro\ImportExport\ImportExport() : new Common\ImportExport\ImportExport(); $this->sitemap = $this->pro ? new Pro\Sitemap\Sitemap() : new Common\Sitemap\Sitemap(); $this->htmlSitemap = new Common\Sitemap\Html\Sitemap(); $this->templates = $this->pro ? new Pro\Utils\Templates() : new Common\Utils\Templates(); $this->categoryBase = new Common\Main\CategoryBase(); $this->postSettings = $this->pro ? new Pro\Admin\PostSettings() : new Lite\Admin\PostSettings(); $this->standalone = new Common\Standalone\Standalone(); $this->searchStatistics = $this->pro ? new Pro\SearchStatistics\SearchStatistics() : new Common\SearchStatistics\SearchStatistics(); $this->slugMonitor = new Common\Admin\SlugMonitor(); $this->schema = $this->pro ? new Pro\Schema\Schema() : new Common\Schema\Schema(); $this->actionScheduler = new Common\Utils\ActionScheduler(); $this->seoRevisions = $this->pro ? new Pro\SeoRevisions\SeoRevisions() : new Common\SeoRevisions\SeoRevisions(); $this->ai = $this->pro ? new Pro\Ai\Ai() : null; $this->filters = $this->pro ? new Pro\Main\Filters() : new Lite\Main\Filters(); $this->crawlCleanup = new Common\QueryArgs\CrawlCleanup(); $this->searchCleanup = new Common\SearchCleanup\SearchCleanup(); $this->emailReports = new Common\EmailReports\EmailReports(); $this->thirdParty = new Common\ThirdParty\ThirdParty(); $this->writingAssistant = new Common\WritingAssistant\WritingAssistant(); if ( ! wp_doing_ajax() && ! wp_doing_cron() ) { $this->rss = new Common\Rss(); $this->main = $this->pro ? new Pro\Main\Main() : new Common\Main\Main(); $this->head = $this->pro ? new Pro\Main\Head() : new Common\Main\Head(); $this->dashboard = $this->pro ? new Pro\Admin\Dashboard() : new Common\Admin\Dashboard(); $this->api = $this->pro ? new Pro\Api\Api() : new Lite\Api\Api(); $this->help = new Common\Help\Help(); } $this->backwardsCompatibilityLoad(); add_action( 'init', [ $this, 'loadInit' ], 999 ); } /** * Things that need to load after init. * * @since 4.0.0 * * @return void */ public function loadInit() { $this->settings = new Common\Utils\VueSettings( '_aioseo_settings' ); $this->sitemap->init(); $this->badBotBlocker->init(); // We call this again to reset any post types/taxonomies that have not yet been set up. $this->dynamicOptions->refresh(); } /** * Loads our addons. * * Runs right after the plugins_loaded hook. * * @since 4.0.0 * * @return void */ public function loadAddons() { do_action( 'aioseo_loaded' ); } } } namespace { // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } /** * The function which returns the one AIOSEO instance. * * @since 4.0.0 * * @return AIOSEO\Plugin\AIOSEO The instance. */ function aioseo() { return AIOSEO\Plugin\AIOSEO::instance(); } }app/Common/Social/Image.php000064400000017435150211766350011541 0ustar00type = $type; $this->post = $post; $this->thumbnailSize = apply_filters( 'aioseo_thumbnail_size', 'fullsize' ); $hash = md5( wp_json_encode( [ $type, $imageSource, $post ] ) ); static $images = []; if ( isset( $images[ $hash ] ) ) { return $images[ $hash ]; } if ( 'auto' === $imageSource && aioseo()->helpers->getPostPageBuilderName( $post->ID ) ) { $imageSource = 'default'; } if ( is_a( $this->post, 'WP_Post' ) ) { switch ( $imageSource ) { case 'featured': $image = $this->getFeaturedImage(); break; case 'attach': $image = $this->getFirstAttachedImage(); break; case 'content': $image = $this->getFirstImageInContent(); break; case 'author': $image = $this->getAuthorAvatar(); break; case 'auto': $image = $this->getFirstAvailableImage(); break; case 'custom': $image = $this->getCustomFieldImage(); break; case 'custom_image': $metaData = aioseo()->meta->metaData->getMetaData( $post ); if ( empty( $metaData ) ) { break; } $image = 'facebook' === strtolower( $this->type ) ? $metaData->og_image_custom_url : $metaData->twitter_image_custom_url; break; case 'default': default: $image = aioseo()->options->social->{$this->type}->general->defaultImagePosts; } } if ( empty( $image ) ) { $image = aioseo()->options->social->{$this->type}->general->defaultImagePosts; } if ( is_array( $image ) ) { $images[ $hash ] = $image; return $images[ $hash ]; } $imageWithoutDimensions = aioseo()->helpers->removeImageDimensions( $image ); $attachmentId = aioseo()->helpers->attachmentUrlToPostId( $imageWithoutDimensions ); $images[ $hash ] = $attachmentId ? wp_get_attachment_image_src( $attachmentId, $this->thumbnailSize ) : $image; return $images[ $hash ]; } /** * Returns the Featured Image for the post. * * @since 4.0.0 * * @return array The image data. */ private function getFeaturedImage() { $cachedImage = $this->getCachedImage(); if ( $cachedImage ) { return $cachedImage; } $imageId = get_post_thumbnail_id( $this->post->ID ); return $imageId ? wp_get_attachment_image_src( $imageId, $this->thumbnailSize ) : ''; } /** * Returns the first attached image. * * @since 4.0.0 * * @return string The image data. */ private function getFirstAttachedImage() { $cachedImage = $this->getCachedImage(); if ( $cachedImage ) { return $cachedImage; } if ( 'attachment' === get_post_type( $this->post->ID ) ) { return wp_get_attachment_image_src( $this->post->ID, $this->thumbnailSize ); } $attachments = get_children( [ 'post_parent' => $this->post->ID, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', ] ); return $attachments && count( $attachments ) ? wp_get_attachment_image_src( array_values( $attachments )[0]->ID, $this->thumbnailSize ) : ''; } /** * Returns the first image found in the post content. * * @since 4.0.0 * * @return string The image URL. */ private function getFirstImageInContent() { $cachedImage = $this->getCachedImage(); if ( $cachedImage ) { return $cachedImage; } $postContent = aioseo()->helpers->getPostContent( $this->post ); preg_match_all( '||i', (string) $postContent, $matches ); // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage // Ignore cover block background image - WP >= 5.7. if ( ! empty( $matches[0] ) && apply_filters( 'aioseo_social_image_ignore_cover_block', true, $this->post, $matches ) ) { foreach ( $matches[0] as $key => $match ) { if ( false !== stripos( $match, 'wp-block-cover__image-background' ) ) { unset( $matches[1][ $key ] ); } } } return ! empty( $matches[1] ) ? current( $matches[1] ) : ''; } /** * Returns the author avatar. * * @since 4.0.0 * * @return string The image URL. */ private function getAuthorAvatar() { $avatar = get_avatar( $this->post->post_author, 300 ); preg_match( "/src='(.*?)'/i", (string) $avatar, $matches ); return ! empty( $matches[1] ) ? $matches[1] : ''; } /** * Returns the first available image. * * @since 4.0.0 * * @return string The image URL. */ private function getFirstAvailableImage() { // Disable the cache. $this->useCache = false; $image = $this->getCustomFieldImage(); if ( ! $image ) { $image = $this->getFeaturedImage(); } if ( ! $image ) { $image = $this->getFirstAttachedImage(); } if ( ! $image ) { $image = $this->getFirstImageInContent(); } if ( ! $image && 'twitter' === strtolower( $this->type ) ) { $image = aioseo()->options->social->twitter->homePage->image; } // Enable the cache. $this->useCache = true; return $image ? $image : aioseo()->options->social->facebook->homePage->image; } /** * Returns the image from a custom field. * * @since 4.0.0 * * @return string The image URL. */ private function getCustomFieldImage() { $cachedImage = $this->getCachedImage(); if ( $cachedImage ) { return $cachedImage; } $prefix = 'facebook' === strtolower( $this->type ) ? 'og_' : 'twitter_'; $aioseoPost = Models\Post::getPost( $this->post->ID ); $customFields = ! empty( $aioseoPost->{ $prefix . 'image_custom_fields' } ) ? $aioseoPost->{ $prefix . 'image_custom_fields' } : aioseo()->options->social->{$this->type}->general->customFieldImagePosts; if ( ! $customFields ) { return ''; } $customFields = explode( ',', $customFields ); foreach ( $customFields as $customField ) { $image = get_post_meta( $this->post->ID, $customField, true ); if ( ! empty( $image ) ) { $image = is_array( $image ) ? $image[0] : $image; return is_numeric( $image ) ? wp_get_attachment_image_src( $image, $this->thumbnailSize ) : $image; } } return ''; } /** * Returns the cached image if there is one. * * @since 4.1.6.2 * * @param \WP_Term $object The object for which we need to get the cached image. * @return string|array The image URL or data. */ protected function getCachedImage( $object = null ) { if ( null === $object ) { // This isn't null if we call it from the Pro class. $object = $this->post; } $metaData = aioseo()->meta->metaData->getMetaData( $object ); switch ( $this->type ) { case 'facebook': if ( ! empty( $metaData->og_image_url ) && $this->useCache ) { return aioseo()->meta->metaData->getCachedOgImage( $metaData ); } break; case 'twitter': if ( ! empty( $metaData->twitter_image_url ) && $this->useCache ) { return $metaData->twitter_image_url; } break; default: break; } return ''; } }app/Common/Social/Social.php000064400000010213150211766350011714 0ustar00image = new Image(); if ( wp_doing_ajax() || wp_doing_cron() ) { return; } $this->facebook = new Facebook(); $this->twitter = new Twitter(); $this->output = new Output(); $this->hooks(); } /** * Registers our hooks. * * @since 4.0.0 */ protected function hooks() { add_action( $this->bustOgCacheActionName, [ $this, 'bustOgCachePost' ] ); // To avoid duplicate sets of meta tags. add_filter( 'jetpack_enable_open_graph', '__return_false' ); if ( ! is_admin() ) { add_filter( 'language_attributes', [ $this, 'addAttributes' ] ); return; } // Forces a refresh of the Facebook cache. add_action( 'post_updated', [ $this, 'scheduleBustOgCachePost' ], 10, 2 ); } /** * Adds our attributes to the registered language attributes. * * @since 4.0.0 * @version 4.4.5 Adds trim function the html tag removing empty spaces. * * @param string $htmlTag The 'html' tag as a string. * @return string The filtered 'html' tag as a string. */ public function addAttributes( $htmlTag ) { if ( ! aioseo()->options->social->facebook->general->enable ) { return $htmlTag; } $attributes = apply_filters( 'aioseo_opengraph_attributes', [ 'prefix="og: https://ogp.me/ns#"' ] ); foreach ( $attributes as $attr ) { if ( strpos( $htmlTag, $attr ) === false ) { $htmlTag .= " $attr "; } } return trim( $htmlTag ); } /** * Schedule a ping to bust the OG cache. * * @since 4.2.0 * * @param int $postId The post ID. * @param \WP_Post $post The post object. * @return void */ public function scheduleBustOgCachePost( $postId, $post = null ) { if ( ! aioseo()->helpers->isSbCustomFacebookFeedActive() || ! aioseo()->helpers->isValidPost( $post ) ) { return; } if ( aioseo()->actionScheduler->isScheduled( $this->bustOgCacheActionName, [ 'postId' => $postId ] ) ) { return; } // Schedule the new ping. aioseo()->actionScheduler->scheduleAsync( $this->bustOgCacheActionName, [ 'postId' => $postId ] ); } /** * Pings Facebook and asks them to bust the OG cache for a particular post. * * @since 4.2.0 * * @see https://developers.facebook.com/docs/sharing/opengraph/using-objects#update * * @param int $postId The post ID. * @return void */ public function bustOgCachePost( $postId ) { $post = get_post( $postId ); $customAccessToken = apply_filters( 'aioseo_facebook_access_token', '' ); if ( ! aioseo()->helpers->isValidPost( $post ) || ( ! aioseo()->helpers->isSbCustomFacebookFeedActive() && ! $customAccessToken ) ) { return; } $permalink = get_permalink( $postId ); $this->bustOgCacheHelper( $permalink ); } /** * Helper function for bustOgCache(). * * @since 4.2.0 * * @param string $permalink The permalink. * @return void */ protected function bustOgCacheHelper( $permalink ) { $accessToken = aioseo()->helpers->getSbAccessToken(); $accessToken = apply_filters( 'aioseo_facebook_access_token', $accessToken ); if ( ! $accessToken ) { return; } $url = sprintf( 'https://graph.facebook.com/?%s', http_build_query( [ 'id' => $permalink, 'scrape' => true, 'access_token' => $accessToken ] ) ); wp_remote_post( $url, [ 'blocking' => false ] ); } }app/Common/Social/Output.php000064400000011065150211766350012010 0ustar00helpers->isWooCommerceShopPage() ) { return false; } return true; } /** * Returns the Open Graph meta. * * @since 4.0.0 * * @return array The Open Graph meta. */ public function getFacebookMeta() { if ( ! $this->isAllowed() || ! aioseo()->options->social->facebook->general->enable ) { return []; } $meta = [ 'og:locale' => aioseo()->social->facebook->getLocale(), 'og:site_name' => aioseo()->helpers->encodeOutputHtml( aioseo()->social->facebook->getSiteName() ), 'og:type' => aioseo()->social->facebook->getObjectType(), 'og:title' => aioseo()->helpers->encodeOutputHtml( aioseo()->social->facebook->getTitle() ), 'og:description' => aioseo()->helpers->encodeOutputHtml( aioseo()->social->facebook->getDescription() ), 'og:url' => esc_url( aioseo()->helpers->canonicalUrl() ), 'fb:app_id' => aioseo()->options->social->facebook->advanced->appId, 'fb:admins' => implode( ',', array_map( 'trim', explode( ',', aioseo()->options->social->facebook->advanced->adminId ) ) ), ]; $image = aioseo()->social->facebook->getImage(); if ( $image ) { $image = is_array( $image ) ? $image[0] : $image; $image = aioseo()->helpers->makeUrlAbsolute( $image ); $image = set_url_scheme( esc_url( $image ) ); $meta += [ 'og:image' => $image, 'og:image:secure_url' => is_ssl() ? $image : '', 'og:image:width' => aioseo()->social->facebook->getImageWidth(), 'og:image:height' => aioseo()->social->facebook->getImageHeight(), ]; } $video = aioseo()->social->facebook->getVideo(); if ( $video ) { $video = set_url_scheme( esc_url( $video ) ); $meta += [ 'og:video' => $video, 'og:video:secure_url' => is_ssl() ? $video : '', 'og:video:width' => aioseo()->social->facebook->getVideoWidth(), 'og:video:height' => aioseo()->social->facebook->getVideoHeight(), ]; } if ( ! empty( $meta['og:type'] ) && 'article' === $meta['og:type'] ) { $meta += [ 'article:section' => aioseo()->social->facebook->getSection(), 'article:tag' => aioseo()->social->facebook->getArticleTags(), 'article:published_time' => aioseo()->social->facebook->getPublishedTime(), 'article:modified_time' => aioseo()->social->facebook->getModifiedTime(), 'article:publisher' => aioseo()->social->facebook->getPublisher(), 'article:author' => aioseo()->social->facebook->getAuthor() ]; } return array_filter( apply_filters( 'aioseo_facebook_tags', $meta ) ); } /** * Returns the Twitter meta. * * @since 4.0.0 * * @return array The Twitter meta. */ public function getTwitterMeta() { if ( ! $this->isAllowed() || ! aioseo()->options->social->twitter->general->enable ) { return []; } $meta = [ 'twitter:card' => aioseo()->social->twitter->getCardType(), 'twitter:site' => aioseo()->social->twitter->prepareUsername( aioseo()->social->twitter->getTwitterUrl() ), 'twitter:title' => aioseo()->helpers->encodeOutputHtml( aioseo()->social->twitter->getTitle() ), 'twitter:description' => aioseo()->helpers->encodeOutputHtml( aioseo()->social->twitter->getDescription() ), 'twitter:creator' => aioseo()->social->twitter->getCreator() ]; $image = aioseo()->social->twitter->getImage(); if ( $image ) { $image = is_array( $image ) ? $image[0] : $image; $image = aioseo()->helpers->makeUrlAbsolute( $image ); // Set the twitter image meta. $meta['twitter:image'] = $image; } if ( is_singular() ) { $additionalData = apply_filters( 'aioseo_social_twitter_additional_data', aioseo()->social->twitter->getAdditionalData() ); if ( $additionalData ) { $i = 1; foreach ( $additionalData as $data ) { $meta[ "twitter:label$i" ] = $data['label']; $meta[ "twitter:data$i" ] = $data['value']; $i++; } } } return array_filter( apply_filters( 'aioseo_twitter_tags', $meta ) ); } }app/Common/Social/Facebook.php000064400000040211150211766350012214 0ustar00helpers->getPost( $postId ); if ( is_home() && 'posts' === get_option( 'show_on_front' ) ) { $image = aioseo()->options->social->facebook->homePage->image; if ( empty( $image ) ) { $image = aioseo()->social->image->getImage( 'facebook', aioseo()->options->social->facebook->general->defaultImageSourcePosts, $post ); } return $image; } $metaData = aioseo()->meta->metaData->getMetaData( $post ); $image = ''; if ( ! empty( $metaData ) ) { $imageSource = ! empty( $metaData->og_image_type ) && 'default' !== $metaData->og_image_type ? $metaData->og_image_type : aioseo()->options->social->facebook->general->defaultImageSourcePosts; $image = aioseo()->social->image->getImage( 'facebook', $imageSource, $post ); } // Since we could be on an archive page, let's check again for that default image. if ( ! $image ) { $image = aioseo()->social->image->getImage( 'facebook', 'default' ); } if ( ! $image ) { $image = aioseo()->helpers->getSiteLogoUrl(); } // Allow users to control the default image per post type. return apply_filters( 'aioseo_opengraph_default_image', $image, [ $post, $this->getObjectType() ] ); } /** * Returns the width of the Open Graph image. * * @since 4.0.0 * * @return string The image width. */ public function getImageWidth() { if ( is_home() && 'posts' === get_option( 'show_on_front' ) ) { $width = aioseo()->options->social->facebook->homePage->imageWidth; return $width ? $width : aioseo()->options->social->facebook->general->defaultImagePostsWidth; } $metaData = aioseo()->meta->metaData->getMetaData(); if ( ! empty( $metaData->og_custom_image_width ) ) { return $metaData->og_custom_image_width; } $image = $this->getImage(); if ( is_array( $image ) ) { return $image[1]; } return aioseo()->options->social->facebook->general->defaultImagePostsWidth; } /** * Returns the height of the Open Graph image. * * @since 4.0.0 * * @return string The image height. */ public function getImageHeight() { if ( is_home() && 'posts' === get_option( 'show_on_front' ) ) { $height = aioseo()->options->social->facebook->homePage->imageHeight; return $height ? $height : aioseo()->options->social->facebook->general->defaultImagePostsHeight; } $metaData = aioseo()->meta->metaData->getMetaData(); if ( ! empty( $metaData->og_custom_image_height ) ) { return $metaData->og_custom_image_height; } $image = $this->getImage(); if ( is_array( $image ) ) { return $image[2]; } return aioseo()->options->social->facebook->general->defaultImagePostsHeight; } /** * Returns the Open Graph video URL. * * @since 4.0.0 * * @return string The video URL. */ public function getVideo() { $metaData = aioseo()->meta->metaData->getMetaData(); return ! empty( $metaData->og_video ) ? $metaData->og_video : ''; } /** * Returns the width of the video. * * @since 4.0.0 * * @return string The video width. */ public function getVideoWidth() { $metaData = aioseo()->meta->metaData->getMetaData(); return ! empty( $metaData->og_video_width ) ? $metaData->og_video_width : ''; } /** * Returns the height of the video. * * @since 4.0.0 * * @return string The video height. */ public function getVideoHeight() { $metaData = aioseo()->meta->metaData->getMetaData(); return ! empty( $metaData->og_video_height ) ? $metaData->og_video_height : ''; } /** * Returns the site name. * * @since 4.0.0 * * @return string The site name. */ public function getSiteName() { $title = aioseo()->helpers->decodeHtmlEntities( aioseo()->tags->replaceTags( aioseo()->options->social->facebook->general->siteName, get_the_ID() ) ); if ( ! $title ) { $title = aioseo()->helpers->decodeHtmlEntities( get_bloginfo( 'name' ) ); } return wp_strip_all_tags( $title ); } /** * Returns the Open Graph object type. * * @since 4.0.0 * * @return string The object type. */ public function getObjectType() { if ( is_home() && 'posts' === get_option( 'show_on_front' ) ) { $type = aioseo()->options->social->facebook->homePage->objectType; return $type ? $type : 'website'; } if ( is_post_type_archive() ) { return 'website'; } $post = aioseo()->helpers->getPost(); $metaData = aioseo()->meta->metaData->getMetaData( $post ); if ( ! empty( $metaData->og_object_type ) && 'default' !== $metaData->og_object_type ) { return $metaData->og_object_type; } $postType = get_post_type(); $dynamicOptions = aioseo()->dynamicOptions->noConflict(); $defaultObjectType = $dynamicOptions->social->facebook->general->postTypes->has( $postType ) ? $dynamicOptions->social->facebook->general->postTypes->$postType->objectType : ''; return ! empty( $defaultObjectType ) ? $defaultObjectType : 'article'; } /** * Returns the Open Graph title for the current page. * * @since 4.0.0 * * @param \WP_Post|integer $post The post object or ID (optional). * @return string The Open Graph title. */ public function getTitle( $post = null ) { if ( is_home() && 'posts' === get_option( 'show_on_front' ) ) { $title = aioseo()->meta->title->helpers->prepare( aioseo()->options->social->facebook->homePage->title ); return $title ? $title : aioseo()->meta->title->getTitle(); } $post = aioseo()->helpers->getPost( $post ); $metaData = aioseo()->meta->metaData->getMetaData( $post ); $title = ''; if ( ! empty( $metaData->og_title ) ) { $title = aioseo()->meta->title->helpers->prepare( $metaData->og_title ); } if ( is_post_type_archive() ) { $postType = get_queried_object(); if ( is_a( $postType, 'WP_Post_Type' ) ) { $dynamicOptions = aioseo()->dynamicOptions->noConflict(); if ( $dynamicOptions->searchAppearance->archives->has( $postType->name ) ) { $title = aioseo()->meta->title->helpers->prepare( aioseo()->dynamicOptions->searchAppearance->archives->{ $postType->name }->title ); } } } return $title ? $title : ( $post ? aioseo()->meta->title->getPostTitle( $post ) : $title ); } /** * Returns the Open Graph description. * * @since 4.0.0 * * @param \WP_Post|integer $post The post object or ID (optional). * @return string The Open Graph description. */ public function getDescription( $post = null ) { if ( is_home() && 'posts' === get_option( 'show_on_front' ) ) { $description = aioseo()->meta->description->helpers->prepare( aioseo()->options->social->facebook->homePage->description ); return $description ? $description : aioseo()->meta->description->getDescription(); } $post = aioseo()->helpers->getPost( $post ); $metaData = aioseo()->meta->metaData->getMetaData( $post ); $description = ''; if ( ! empty( $metaData->og_description ) ) { $description = aioseo()->meta->description->helpers->prepare( $metaData->og_description ); } if ( is_post_type_archive() ) { $postType = get_queried_object(); if ( is_a( $postType, 'WP_Post_Type' ) ) { $dynamicOptions = aioseo()->dynamicOptions->noConflict(); if ( $dynamicOptions->searchAppearance->archives->has( $postType->name ) ) { $description = aioseo()->meta->description->helpers->prepare( aioseo()->dynamicOptions->searchAppearance->archives->{ $postType->name }->metaDescription ); } } } return $description ? $description : ( $post ? aioseo()->meta->description->getPostDescription( $post ) : $description ); } /** * Returns the Open Graph article section name. * * @since 4.0.0 * * @return string The article section name. */ public function getSection() { $metaData = aioseo()->meta->metaData->getMetaData(); return ! empty( $metaData->og_article_section ) ? $metaData->og_article_section : ''; } /** * Returns the Open Graph publisher URL. * * @since 4.0.0 * * @return string The Open Graph publisher URL. */ public function getPublisher() { if ( ! aioseo()->options->social->profiles->sameUsername->enable ) { return aioseo()->options->social->profiles->urls->facebookPageUrl; } $username = aioseo()->options->social->profiles->sameUsername->username; return ( $username && in_array( 'facebookPageUrl', aioseo()->options->social->profiles->sameUsername->included, true ) ) ? 'https://facebook.com/' . $username : ''; } /** * Returns the published time. * * @since 4.0.0 * * @return string The published time. */ public function getPublishedTime() { $post = aioseo()->helpers->getPost(); return $post ? aioseo()->helpers->dateTimeToIso8601( $post->post_date_gmt ) : ''; } /** * Returns the last modified time. * * @since 4.0.0 * * @return string The last modified time. */ public function getModifiedTime() { $post = aioseo()->helpers->getPost(); return $post ? aioseo()->helpers->dateTimeToIso8601( $post->post_modified_gmt ) : ''; } /** * Returns the Open Graph author. * * @since 4.0.0 * * @return string The Open Graph author. */ public function getAuthor() { $post = aioseo()->helpers->getPost(); if ( ! is_a( $post, 'WP_Post' ) || ! aioseo()->options->social->facebook->general->showAuthor ) { return ''; } $author = ''; $userProfiles = $this->getUserProfiles( $post->post_author ); if ( ! empty( $userProfiles['facebookPageUrl'] ) ) { $author = $userProfiles['facebookPageUrl']; } if ( empty( $author ) ) { $author = aioseo()->options->social->facebook->advanced->authorUrl; } return $author; } /** * Returns the Open Graph article tags. * * @since 4.0.0 * * @return array An array of unique keywords. */ public function getArticleTags() { $post = aioseo()->helpers->getPost(); $metaData = aioseo()->meta->metaData->getMetaData( $post ); $tags = ! empty( $metaData->og_article_tags ) ? aioseo()->meta->keywords->extractMetaKeywords( $metaData->og_article_tags ) : []; if ( $post && aioseo()->options->social->facebook->advanced->enable && aioseo()->options->social->facebook->advanced->generateArticleTags ) { if ( aioseo()->options->social->facebook->advanced->useKeywordsInTags ) { $keywords = aioseo()->meta->keywords->getKeywords(); $keywords = aioseo()->tags->parseCustomFields( $keywords ); $keywords = aioseo()->meta->keywords->keywordStringToList( $keywords ); $tags = array_merge( $tags, $keywords ); } if ( aioseo()->options->social->facebook->advanced->useCategoriesInTags ) { $tags = array_merge( $tags, aioseo()->helpers->getAllCategories( $post->ID ) ); } if ( aioseo()->options->social->facebook->advanced->usePostTagsInTags ) { $tags = array_merge( $tags, aioseo()->helpers->getAllTags( $post->ID ) ); } } return aioseo()->meta->keywords->getUniqueKeywords( $tags, false ); } /** * Retreive the locale. * * @since 4.1.4 * * @return string The locale. */ public function getLocale() { $locale = get_locale(); // These are the locales FB supports. $validLocales = [ 'af_ZA', // Afrikaans. 'ak_GH', // Akan. 'am_ET', // Amharic. 'ar_AR', // Arabic. 'as_IN', // Assamese. 'ay_BO', // Aymara. 'az_AZ', // Azerbaijani. 'be_BY', // Belarusian. 'bg_BG', // Bulgarian. 'bp_IN', // Bhojpuri. 'bn_IN', // Bengali. 'br_FR', // Breton. 'bs_BA', // Bosnian. 'ca_ES', // Catalan. 'cb_IQ', // Sorani Kurdish. 'ck_US', // Cherokee. 'co_FR', // Corsican. 'cs_CZ', // Czech. 'cx_PH', // Cebuano. 'cy_GB', // Welsh. 'da_DK', // Danish. 'de_DE', // German. 'el_GR', // Greek. 'en_GB', // English (UK). 'en_PI', // English (Pirate). 'en_UD', // English (Upside Down). 'en_US', // English (US). 'em_ZM', 'eo_EO', // Esperanto. 'es_ES', // Spanish (Spain). 'es_LA', // Spanish. 'es_MX', // Spanish (Mexico). 'et_EE', // Estonian. 'eu_ES', // Basque. 'fa_IR', // Persian. 'fb_LT', // Leet Speak. 'ff_NG', // Fulah. 'fi_FI', // Finnish. 'fo_FO', // Faroese. 'fr_CA', // French (Canada). 'fr_FR', // French (France). 'fy_NL', // Frisian. 'ga_IE', // Irish. 'gl_ES', // Galician. 'gn_PY', // Guarani. 'gu_IN', // Gujarati. 'gx_GR', // Classical Greek. 'ha_NG', // Hausa. 'he_IL', // Hebrew. 'hi_IN', // Hindi. 'hr_HR', // Croatian. 'hu_HU', // Hungarian. 'ht_HT', // Haitian Creole. 'hy_AM', // Armenian. 'id_ID', // Indonesian. 'ig_NG', // Igbo. 'is_IS', // Icelandic. 'it_IT', // Italian. 'ik_US', 'iu_CA', 'ja_JP', // Japanese. 'ja_KS', // Japanese (Kansai). 'jv_ID', // Javanese. 'ka_GE', // Georgian. 'kk_KZ', // Kazakh. 'km_KH', // Khmer. 'kn_IN', // Kannada. 'ko_KR', // Korean. 'ks_IN', // Kashmiri. 'ku_TR', // Kurdish (Kurmanji). 'ky_KG', // Kyrgyz. 'la_VA', // Latin. 'lg_UG', // Ganda. 'li_NL', // Limburgish. 'ln_CD', // Lingala. 'lo_LA', // Lao. 'lt_LT', // Lithuanian. 'lv_LV', // Latvian. 'mg_MG', // Malagasy. 'mi_NZ', // Maori. 'mk_MK', // Macedonian. 'ml_IN', // Malayalam. 'mn_MN', // Mongolian. 'mr_IN', // Marathi. 'ms_MY', // Malay. 'mt_MT', // Maltese. 'my_MM', // Burmese. 'nb_NO', // Norwegian (bokmal). 'nd_ZW', // Ndebele. 'ne_NP', // Nepali. 'nl_BE', // Dutch (Belgie). 'nl_NL', // Dutch. 'nn_NO', // Norwegian (nynorsk). 'nr_ZA', // Southern Ndebele. 'ns_ZA', // Northern Sotho. 'ny_MW', // Chewa. 'om_ET', // Oromo. 'or_IN', // Oriya. 'pa_IN', // Punjabi. 'pl_PL', // Polish. 'ps_AF', // Pashto. 'pt_BR', // Portuguese (Brazil). 'pt_PT', // Portuguese (Portugal). 'qc_GT', // Quiché. 'qu_PE', // Quechua. 'qr_GR', 'qz_MM', // Burmese (Zawgyi). 'rm_CH', // Romansh. 'ro_RO', // Romanian. 'ru_RU', // Russian. 'rw_RW', // Kinyarwanda. 'sa_IN', // Sanskrit. 'sc_IT', // Sardinian. 'se_NO', // Northern Sami. 'si_LK', // Sinhala. 'su_ID', // Sundanese. 'sk_SK', // Slovak. 'sl_SI', // Slovenian. 'sn_ZW', // Shona. 'so_SO', // Somali. 'sq_AL', // Albanian. 'sr_RS', // Serbian. 'ss_SZ', // Swazi. 'st_ZA', // Southern Sotho. 'sv_SE', // Swedish. 'sw_KE', // Swahili. 'sy_SY', // Syriac. 'sz_PL', // Silesian. 'ta_IN', // Tamil. 'te_IN', // Telugu. 'tg_TJ', // Tajik. 'th_TH', // Thai. 'tk_TM', // Turkmen. 'tl_PH', // Filipino. 'tl_ST', // Klingon. 'tn_BW', // Tswana. 'tr_TR', // Turkish. 'ts_ZA', // Tsonga. 'tt_RU', // Tatar. 'tz_MA', // Tamazight. 'uk_UA', // Ukrainian. 'ur_PK', // Urdu. 'uz_UZ', // Uzbek. 've_ZA', // Venda. 'vi_VN', // Vietnamese. 'wo_SN', // Wolof. 'xh_ZA', // Xhosa. 'yi_DE', // Yiddish. 'yo_NG', // Yoruba. 'zh_CN', // Simplified Chinese (China). 'zh_HK', // Traditional Chinese (Hong Kong). 'zh_TW', // Traditional Chinese (Taiwan). 'zu_ZA', // Zulu. 'zz_TR', // Zazaki. ]; // Catch some weird locales served out by WP that are not easily doubled up. $fixLocales = [ 'ca' => 'ca_ES', 'en' => 'en_US', 'el' => 'el_GR', 'et' => 'et_EE', 'ja' => 'ja_JP', 'sq' => 'sq_AL', 'uk' => 'uk_UA', 'vi' => 'vi_VN', 'zh' => 'zh_CN', ]; if ( isset( $fixLocales[ $locale ] ) ) { $locale = $fixLocales[ $locale ]; } // Convert locales like "es" to "es_ES", in case that works for the given locale (sometimes it does). if ( 2 === strlen( $locale ) ) { $locale = strtolower( $locale ) . '_' . strtoupper( $locale ); } // Check to see if the locale is a valid FB one, if not, use en_US as a fallback. if ( ! in_array( $locale, $validLocales, true ) ) { $locale = strtolower( substr( $locale, 0, 2 ) ) . '_' . strtoupper( substr( $locale, 0, 2 ) ); if ( ! in_array( $locale, $validLocales, true ) ) { $locale = 'en_US'; } } return apply_filters( 'aioseo_og_locale', $locale ); } }app/Common/Social/Twitter.php000064400000017724150211766350012162 0ustar00options->social->profiles->sameUsername->enable ) { return aioseo()->options->social->profiles->urls->twitterUrl; } $userName = aioseo()->options->social->profiles->sameUsername->username; return ( $userName && in_array( 'twitterUrl', aioseo()->options->social->profiles->sameUsername->included, true ) ) ? 'https://x.com/' . $userName : ''; } /** * Returns the Twitter card type. * * @since 4.0.0 * * @return string $card The card type. */ public function getCardType() { if ( is_home() && 'posts' === get_option( 'show_on_front' ) ) { return aioseo()->options->social->twitter->homePage->cardType; } $metaData = aioseo()->meta->metaData->getMetaData(); return ! empty( $metaData->twitter_card ) && 'default' !== $metaData->twitter_card ? $metaData->twitter_card : aioseo()->options->social->twitter->general->defaultCardType; } /** * Returns the Twitter creator. * * @since 4.0.0 * * @return string The creator. */ public function getCreator() { $post = aioseo()->helpers->getPost(); if ( ! is_a( $post, 'WP_Post' ) || ! post_type_supports( $post->post_type, 'author' ) || ! aioseo()->options->social->twitter->general->showAuthor ) { return ''; } $author = ''; $userProfiles = $this->getUserProfiles( $post->post_author ); if ( ! empty( $userProfiles['twitterUrl'] ) ) { $author = $userProfiles['twitterUrl']; } if ( empty( $author ) ) { $author = aioseo()->social->twitter->getTwitterUrl(); } $author = aioseo()->social->twitter->prepareUsername( $author ); return $author; } /** * Returns the Twitter image URL. * * @since 4.0.0 * * @param int $postId The post ID (optional). * @return string The image URL. */ public function getImage( $postId = null ) { $post = aioseo()->helpers->getPost( $postId ); if ( is_home() && 'posts' === get_option( 'show_on_front' ) ) { $image = aioseo()->options->social->twitter->homePage->image; if ( empty( $image ) ) { $image = aioseo()->options->social->facebook->homePage->image; } if ( empty( $image ) ) { $image = aioseo()->social->image->getImage( 'twitter', aioseo()->options->social->twitter->general->defaultImageSourcePosts, $post ); } return $image ? $image : aioseo()->social->facebook->getImage(); } $metaData = aioseo()->meta->metaData->getMetaData( $post ); if ( ! empty( $metaData->twitter_use_og ) ) { return aioseo()->social->facebook->getImage(); } $image = ''; if ( ! empty( $metaData ) ) { $imageSource = ! empty( $metaData->twitter_image_type ) && 'default' !== $metaData->twitter_image_type ? $metaData->twitter_image_type : aioseo()->options->social->twitter->general->defaultImageSourcePosts; $image = aioseo()->social->image->getImage( 'twitter', $imageSource, $post ); } return $image ? $image : aioseo()->social->facebook->getImage(); } /** * Returns the Twitter title for the current page. * * @since 4.0.0 * * @param \WP_Post|integer $post The post object or ID (optional). * @return string The Twitter title. */ public function getTitle( $post = null ) { if ( is_home() && 'posts' === get_option( 'show_on_front' ) ) { $title = aioseo()->meta->title->helpers->prepare( aioseo()->options->social->twitter->homePage->title ); return $title ? $title : aioseo()->social->facebook->getTitle( $post ); } $post = aioseo()->helpers->getPost( $post ); $metaData = aioseo()->meta->metaData->getMetaData( $post ); if ( ! empty( $metaData->twitter_use_og ) ) { return aioseo()->social->facebook->getTitle( $post ); } $title = ''; if ( ! empty( $metaData->twitter_title ) ) { $title = aioseo()->meta->title->helpers->prepare( $metaData->twitter_title ); } return $title ? $title : aioseo()->social->facebook->getTitle( $post ); } /** * Returns the Twitter description for the current page. * * @since 4.0.0 * * @param \WP_Post|integer $post The post object or ID (optional). * @return string The Twitter description. */ public function getDescription( $post = null ) { if ( is_home() && 'posts' === get_option( 'show_on_front' ) ) { $description = aioseo()->meta->description->helpers->prepare( aioseo()->options->social->twitter->homePage->description ); return $description ? $description : aioseo()->social->facebook->getDescription( $post ); } $post = aioseo()->helpers->getPost( $post ); $metaData = aioseo()->meta->metaData->getMetaData( $post ); if ( ! empty( $metaData->twitter_use_og ) ) { return aioseo()->social->facebook->getDescription( $post ); } $description = ''; if ( ! empty( $metaData->twitter_description ) ) { $description = aioseo()->meta->description->helpers->prepare( $metaData->twitter_description ); } return $description ? $description : aioseo()->social->facebook->getDescription( $post ); } /** * Prepare twitter username for public display. * * We do things like strip out the URL, etc and return just (at)username. * At the moment, we'll check for 1 of 3 things... (at)username, username, and https://x.com/username. * * @since 4.0.0 * * @param string $profile Twitter username. * @param boolean $includeAt Whether or not ot include the @ sign. * @return string Full Twitter username. */ public function prepareUsername( $profile, $includeAt = true ) { if ( ! $profile ) { return $profile; } $profile = (string) $profile; if ( preg_match( '/^(\@)?[A-Za-z0-9_]+$/', (string) $profile ) ) { if ( '@' !== $profile[0] && $includeAt ) { $profile = '@' . $profile; } elseif ( '@' === $profile[0] && ! $includeAt ) { $profile = ltrim( $profile, '@' ); } } if ( strpos( $profile, 'twitter.com' ) || strpos( $profile, 'x.com' ) ) { $profile = esc_url( $profile ); // Extract the twitter username from the URL. $parsedTwitterProfile = wp_parse_url( $profile ); $path = $parsedTwitterProfile['path']; $pathParts = explode( '/', $path ); $profile = $pathParts[1]; if ( $profile ) { if ( '@' !== $profile[0] && $includeAt ) { $profile = '@' . $profile; } if ( '@' === $profile[0] && ! $includeAt ) { $profile = ltrim( $profile, '@' ); } } } return $profile; } /** * Get additional twitter data. * * @since 4.0.0 * * @return array An array of additional twitter data. */ public function getAdditionalData() { if ( ! aioseo()->options->social->twitter->general->additionalData ) { return []; } $data = []; $post = aioseo()->helpers->getPost(); if ( ! is_a( $post, 'WP_Post' ) ) { return $data; } if ( $post->post_author && post_type_supports( $post->post_type, 'author' ) ) { $data[] = [ 'label' => __( 'Written by', 'all-in-one-seo-pack' ), 'value' => get_the_author_meta( 'display_name', $post->post_author ) ]; } if ( ! empty( $post->post_content ) ) { $minutes = $this->getReadingTime( $post->post_content ); if ( ! empty( $minutes ) ) { $data[] = [ 'label' => __( 'Est. reading time', 'all-in-one-seo-pack' ), // Translators: 1 - The estimated reading time. 'value' => sprintf( _n( '%1$s minute', '%1$s minutes', $minutes, 'all-in-one-seo-pack' ), $minutes ) ]; } } return $data; } /** * Returns the estimated reading time for a string. * * @since 4.0.0 * * @param string $string The string to count. * @return integer The estimated reading time as an integer. */ private function getReadingTime( $string ) { $wpm = 200; $word = str_word_count( wp_strip_all_tags( $string ) ); return round( $word / $wpm ); } }app/Common/WritingAssistant/Utils/Helpers.php000064400000003036150211766350015314 0ustar00 get_the_ID(), 'report' => $keyword, 'keywordText' => ! empty( $keyword->keyword ) ? $keyword->keyword : '', 'contentAnalysis' => Models\WritingAssistantPost::getContentAnalysis( get_the_ID() ), 'seoBoost' => [ 'isLoggedIn' => aioseo()->writingAssistant->seoBoost->isLoggedIn(), 'loginUrl' => aioseo()->writingAssistant->seoBoost->getLoginUrl(), 'createAccountUrl' => aioseo()->writingAssistant->seoBoost->getCreateAccountUrl(), 'userOptions' => aioseo()->writingAssistant->seoBoost->getUserOptions() ] ]; } /** * Gets the data for vue. * * @since 4.7.4 * * @return array An array of data. */ public function getSettingsVueData() { return [ 'seoBoost' => [ 'isLoggedIn' => aioseo()->writingAssistant->seoBoost->isLoggedIn(), 'loginUrl' => aioseo()->writingAssistant->seoBoost->getLoginUrl(), 'createAccountUrl' => aioseo()->writingAssistant->seoBoost->getCreateAccountUrl(), 'userOptions' => aioseo()->writingAssistant->seoBoost->getUserOptions() ] ]; } }app/Common/WritingAssistant/SeoBoost/SeoBoost.php000064400000057732150211766350016120 0ustar00service = new Service(); $returnParam = isset( $_GET['aioseo-writing-assistant'] ) // phpcs:ignore HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended ? sanitize_text_field( wp_unslash( $_GET['aioseo-writing-assistant'] ) ) // phpcs:ignore HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended : null; if ( 'auth_return' === $returnParam ) { add_action( 'init', [ $this, 'checkToken' ], 50 ); } if ( 'ms_logged_in' === $returnParam ) { add_action( 'init', [ $this, 'marketingSiteCallback' ], 50 ); } add_action( 'init', [ $this, 'migrateUserData' ], 10 ); add_action( 'init', [ $this, 'refreshUserOptionsAfterError' ] ); } /** * Returns if the user has an access key. * * @since 4.7.4 * * @return bool */ public function isLoggedIn() { return $this->getAccessToken() !== ''; } /** * Gets the login URL. * * @since 4.7.4 * * @return string The login URL. */ public function getLoginUrl() { $url = $this->loginUrl; if ( defined( 'AIOSEO_WRITING_ASSISTANT_LOGIN_URL' ) ) { $url = AIOSEO_WRITING_ASSISTANT_LOGIN_URL; } $params = [ 'oauth' => true, 'redirect' => get_site_url() . '?' . build_query( [ 'aioseo-writing-assistant' => 'auth_return' ] ), 'domain' => aioseo()->helpers->getMultiSiteDomain() ]; return trailingslashit( $url ) . '?' . build_query( $params ); } /** * Gets the login URL. * * @since 4.7.4 * * @return string The login URL. */ public function getCreateAccountUrl() { $url = $this->createAccountUrl; if ( defined( 'AIOSEO_WRITING_ASSISTANT_CREATE_ACCOUNT_URL' ) ) { $url = AIOSEO_WRITING_ASSISTANT_CREATE_ACCOUNT_URL; } $params = [ 'url' => base64_encode( get_site_url() . '?' . build_query( [ 'aioseo-writing-assistant' => 'ms_logged_in' ] ) ), 'writing-assistant-checkout' => true ]; return trailingslashit( $url ) . '?' . build_query( $params ); } /** * Gets the user's access token. * * @since 4.7.4 * * @return string The access token. */ public function getAccessToken() { $metaKey = 'seoboost_access_token_' . get_current_blog_id(); return get_user_meta( get_current_user_id(), $metaKey, true ); } /** * Sets the user's access token. * * @since 4.7.4 * * @return void */ public function setAccessToken( $accessToken ) { $metaKey = 'seoboost_access_token_' . get_current_blog_id(); update_user_meta( get_current_user_id(), $metaKey, $accessToken ); $this->refreshUserOptions(); } /** * Refreshes user options from SEOBoost. * * @since 4.7.4 * * @return void */ public function refreshUserOptions() { $userOptions = $this->service->getUserOptions(); if ( is_wp_error( $userOptions ) || ! empty( $userOptions['error'] ) ) { $userOptions = $this->getDefaultUserOptions(); aioseo()->cache->update( 'seoboost_get_user_options_error', time() + DAY_IN_SECONDS, MONTH_IN_SECONDS ); } $this->setUserOptions( $userOptions ); } /** * Gets the user options. * * @since 4.7.4 * * @param bool $refresh Whether to refresh the user options. * @return array The user options. */ public function getUserOptions( $refresh = false ) { if ( ! $refresh ) { $metaKey = 'seoboost_user_options_' . get_current_blog_id(); $userOptions = get_user_meta( get_current_user_id(), $metaKey, true ); if ( ! empty( $userOptions ) ) { return json_decode( (string) $userOptions, true ) ?? []; } } // If there are no options or we need to refresh them, get them from SEOBoost. $this->refreshUserOptions(); $userOptions = $this->getUserOptions(); if ( empty( $userOptions ) ) { return $this->getDefaultUserOptions(); } return $userOptions; } /** * Gets the user options. * * @since 4.7.4 * * @param array $options The user options. * @return void */ public function setUserOptions( $options ) { if ( ! is_array( $options ) ) { return; } $userOptions = array_merge( $this->getDefaultUserOptions(), $options ); $metaKey = 'seoboost_user_options_' . get_current_blog_id(); update_user_meta( get_current_user_id(), $metaKey, wp_json_encode( $userOptions ) ); } /** * Gets the user info from SEOBoost. * * @since 4.7.4 * * @return array|\WP_Error The user info or a WP_Error. */ public function getUserInfo() { return $this->service->getUserInfo(); } /** * Checks the token. * * @since 4.7.4 * * @return void */ public function checkToken() { $authToken = isset( $_GET['token'] ) // phpcs:ignore HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended ? sanitize_key( wp_unslash( $_GET['token'] ) ) // phpcs:ignore HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended : null; if ( $authToken ) { $accessToken = $this->service->getAccessToken( $authToken ); if ( ! is_wp_error( $accessToken ) && ! empty( $accessToken['token'] ) ) { $this->setAccessToken( $accessToken['token'] ); ?> core->db->delete( 'usermeta' )->whereRaw( 'meta_key LIKE \'seoboost_access_token%\'' )->run(); aioseo()->core->db->delete( 'usermeta' )->where( 'meta_key', 'seoboost_user_options' )->run(); } /** * Gets the report history. * * @since 4.7.4 * * @return array|\WP_Error The report history. */ public function getReportHistory() { return $this->service->getReportHistory(); } /** * Migrate Writing Assistant access tokens. * This handles the fix for multisites where subsites all used the same workspace/account. * * @since 4.7.7 * * @return void */ public function migrateUserData() { $userToken = get_user_meta( get_current_user_id(), 'seoboost_access_token', true ); if ( ! empty( $userToken ) ) { $this->setAccessToken( $userToken ); delete_user_meta( get_current_user_id(), 'seoboost_access_token' ); } $userOptions = get_user_meta( get_current_user_id(), 'seoboost_user_options', true ); if ( ! empty( $userOptions ) ) { $this->setUserOptions( $userOptions ); delete_user_meta( get_current_user_id(), 'seoboost_user_options' ); } } /** * Refreshes user options after an error. * This needs to run on init since service class is not available in the constructor. * * @since 4.7.7.2 * * @return void */ public function refreshUserOptionsAfterError() { $userOptionsFetchError = aioseo()->cache->get( 'seoboost_get_user_options_error' ); if ( $userOptionsFetchError && time() > $userOptionsFetchError ) { aioseo()->cache->delete( 'seoboost_get_user_options_error' ); $this->refreshUserOptions(); } } /** * Returns the default user options. * * @since 4.7.7.1 * * @return array The default user options. */ private function getDefaultUserOptions() { return [ 'language' => 'en', 'country' => 'US', 'countries' => $this->getCountries(), 'languages' => $this->getLanguages(), 'searchEngines' => $this->getSearchEngines() ]; } /** * Returns the list of countries. * * @since 4.7.7.1 * * @return array The list of countries. */ private function getCountries() { $countries = [ 'AF' => __( 'Afghanistan', 'all-in-one-seo-pack' ), 'AL' => __( 'Albania', 'all-in-one-seo-pack' ), 'DZ' => __( 'Algeria', 'all-in-one-seo-pack' ), 'AS' => __( 'American Samoa', 'all-in-one-seo-pack' ), 'AD' => __( 'Andorra', 'all-in-one-seo-pack' ), 'AO' => __( 'Angola', 'all-in-one-seo-pack' ), 'AI' => __( 'Anguilla', 'all-in-one-seo-pack' ), 'AG' => __( 'Antigua & Barbuda', 'all-in-one-seo-pack' ), 'AR' => __( 'Argentina', 'all-in-one-seo-pack' ), 'AM' => __( 'Armenia', 'all-in-one-seo-pack' ), 'AU' => __( 'Australia', 'all-in-one-seo-pack' ), 'AT' => __( 'Austria', 'all-in-one-seo-pack' ), 'AZ' => __( 'Azerbaijan', 'all-in-one-seo-pack' ), 'BS' => __( 'Bahamas', 'all-in-one-seo-pack' ), 'BH' => __( 'Bahrain', 'all-in-one-seo-pack' ), 'BD' => __( 'Bangladesh', 'all-in-one-seo-pack' ), 'BY' => __( 'Belarus', 'all-in-one-seo-pack' ), 'BE' => __( 'Belgium', 'all-in-one-seo-pack' ), 'BZ' => __( 'Belize', 'all-in-one-seo-pack' ), 'BJ' => __( 'Benin', 'all-in-one-seo-pack' ), 'BT' => __( 'Bhutan', 'all-in-one-seo-pack' ), 'BO' => __( 'Bolivia', 'all-in-one-seo-pack' ), 'BA' => __( 'Bosnia & Herzegovina', 'all-in-one-seo-pack' ), 'BW' => __( 'Botswana', 'all-in-one-seo-pack' ), 'BR' => __( 'Brazil', 'all-in-one-seo-pack' ), 'VG' => __( 'British Virgin Islands', 'all-in-one-seo-pack' ), 'BN' => __( 'Brunei', 'all-in-one-seo-pack' ), 'BG' => __( 'Bulgaria', 'all-in-one-seo-pack' ), 'BF' => __( 'Burkina Faso', 'all-in-one-seo-pack' ), 'BI' => __( 'Burundi', 'all-in-one-seo-pack' ), 'KH' => __( 'Cambodia', 'all-in-one-seo-pack' ), 'CM' => __( 'Cameroon', 'all-in-one-seo-pack' ), 'CA' => __( 'Canada', 'all-in-one-seo-pack' ), 'CV' => __( 'Cape Verde', 'all-in-one-seo-pack' ), 'CF' => __( 'Central African Republic', 'all-in-one-seo-pack' ), 'TD' => __( 'Chad', 'all-in-one-seo-pack' ), 'CL' => __( 'Chile', 'all-in-one-seo-pack' ), 'CO' => __( 'Colombia', 'all-in-one-seo-pack' ), 'CG' => __( 'Congo - Brazzaville', 'all-in-one-seo-pack' ), 'CD' => __( 'Congo - Kinshasa', 'all-in-one-seo-pack' ), 'CK' => __( 'Cook Islands', 'all-in-one-seo-pack' ), 'CR' => __( 'Costa Rica', 'all-in-one-seo-pack' ), 'CI' => __( 'Côte d’Ivoire', 'all-in-one-seo-pack' ), 'HR' => __( 'Croatia', 'all-in-one-seo-pack' ), 'CU' => __( 'Cuba', 'all-in-one-seo-pack' ), 'CY' => __( 'Cyprus', 'all-in-one-seo-pack' ), 'CZ' => __( 'Czechia', 'all-in-one-seo-pack' ), 'DK' => __( 'Denmark', 'all-in-one-seo-pack' ), 'DJ' => __( 'Djibouti', 'all-in-one-seo-pack' ), 'DM' => __( 'Dominica', 'all-in-one-seo-pack' ), 'DO' => __( 'Dominican Republic', 'all-in-one-seo-pack' ), 'EC' => __( 'Ecuador', 'all-in-one-seo-pack' ), 'EG' => __( 'Egypt', 'all-in-one-seo-pack' ), 'SV' => __( 'El Salvador', 'all-in-one-seo-pack' ), 'EE' => __( 'Estonia', 'all-in-one-seo-pack' ), 'ET' => __( 'Ethiopia', 'all-in-one-seo-pack' ), 'FJ' => __( 'Fiji', 'all-in-one-seo-pack' ), 'FI' => __( 'Finland', 'all-in-one-seo-pack' ), 'FR' => __( 'France', 'all-in-one-seo-pack' ), 'GA' => __( 'Gabon', 'all-in-one-seo-pack' ), 'GM' => __( 'Gambia', 'all-in-one-seo-pack' ), 'GE' => __( 'Georgia', 'all-in-one-seo-pack' ), 'DE' => __( 'Germany', 'all-in-one-seo-pack' ), 'GH' => __( 'Ghana', 'all-in-one-seo-pack' ), 'GI' => __( 'Gibraltar', 'all-in-one-seo-pack' ), 'GR' => __( 'Greece', 'all-in-one-seo-pack' ), 'GL' => __( 'Greenland', 'all-in-one-seo-pack' ), 'GT' => __( 'Guatemala', 'all-in-one-seo-pack' ), 'GG' => __( 'Guernsey', 'all-in-one-seo-pack' ), 'GY' => __( 'Guyana', 'all-in-one-seo-pack' ), 'HT' => __( 'Haiti', 'all-in-one-seo-pack' ), 'HN' => __( 'Honduras', 'all-in-one-seo-pack' ), 'HK' => __( 'Hong Kong', 'all-in-one-seo-pack' ), 'HU' => __( 'Hungary', 'all-in-one-seo-pack' ), 'IS' => __( 'Iceland', 'all-in-one-seo-pack' ), 'IN' => __( 'India', 'all-in-one-seo-pack' ), 'ID' => __( 'Indonesia', 'all-in-one-seo-pack' ), 'IQ' => __( 'Iraq', 'all-in-one-seo-pack' ), 'IE' => __( 'Ireland', 'all-in-one-seo-pack' ), 'IM' => __( 'Isle of Man', 'all-in-one-seo-pack' ), 'IL' => __( 'Israel', 'all-in-one-seo-pack' ), 'IT' => __( 'Italy', 'all-in-one-seo-pack' ), 'JM' => __( 'Jamaica', 'all-in-one-seo-pack' ), 'JP' => __( 'Japan', 'all-in-one-seo-pack' ), 'JE' => __( 'Jersey', 'all-in-one-seo-pack' ), 'JO' => __( 'Jordan', 'all-in-one-seo-pack' ), 'KZ' => __( 'Kazakhstan', 'all-in-one-seo-pack' ), 'KE' => __( 'Kenya', 'all-in-one-seo-pack' ), 'KI' => __( 'Kiribati', 'all-in-one-seo-pack' ), 'KW' => __( 'Kuwait', 'all-in-one-seo-pack' ), 'KG' => __( 'Kyrgyzstan', 'all-in-one-seo-pack' ), 'LA' => __( 'Laos', 'all-in-one-seo-pack' ), 'LV' => __( 'Latvia', 'all-in-one-seo-pack' ), 'LB' => __( 'Lebanon', 'all-in-one-seo-pack' ), 'LS' => __( 'Lesotho', 'all-in-one-seo-pack' ), 'LY' => __( 'Libya', 'all-in-one-seo-pack' ), 'LI' => __( 'Liechtenstein', 'all-in-one-seo-pack' ), 'LT' => __( 'Lithuania', 'all-in-one-seo-pack' ), 'LU' => __( 'Luxembourg', 'all-in-one-seo-pack' ), 'MG' => __( 'Madagascar', 'all-in-one-seo-pack' ), 'MW' => __( 'Malawi', 'all-in-one-seo-pack' ), 'MY' => __( 'Malaysia', 'all-in-one-seo-pack' ), 'MV' => __( 'Maldives', 'all-in-one-seo-pack' ), 'ML' => __( 'Mali', 'all-in-one-seo-pack' ), 'MT' => __( 'Malta', 'all-in-one-seo-pack' ), 'MU' => __( 'Mauritius', 'all-in-one-seo-pack' ), 'MX' => __( 'Mexico', 'all-in-one-seo-pack' ), 'FM' => __( 'Micronesia', 'all-in-one-seo-pack' ), 'MD' => __( 'Moldova', 'all-in-one-seo-pack' ), 'MN' => __( 'Mongolia', 'all-in-one-seo-pack' ), 'ME' => __( 'Montenegro', 'all-in-one-seo-pack' ), 'MS' => __( 'Montserrat', 'all-in-one-seo-pack' ), 'MA' => __( 'Morocco', 'all-in-one-seo-pack' ), 'MZ' => __( 'Mozambique', 'all-in-one-seo-pack' ), 'MM' => __( 'Myanmar (Burma)', 'all-in-one-seo-pack' ), 'NA' => __( 'Namibia', 'all-in-one-seo-pack' ), 'NR' => __( 'Nauru', 'all-in-one-seo-pack' ), 'NP' => __( 'Nepal', 'all-in-one-seo-pack' ), 'NL' => __( 'Netherlands', 'all-in-one-seo-pack' ), 'NZ' => __( 'New Zealand', 'all-in-one-seo-pack' ), 'NI' => __( 'Nicaragua', 'all-in-one-seo-pack' ), 'NE' => __( 'Niger', 'all-in-one-seo-pack' ), 'NG' => __( 'Nigeria', 'all-in-one-seo-pack' ), 'NU' => __( 'Niue', 'all-in-one-seo-pack' ), 'MK' => __( 'North Macedonia', 'all-in-one-seo-pack' ), 'NO' => __( 'Norway', 'all-in-one-seo-pack' ), 'OM' => __( 'Oman', 'all-in-one-seo-pack' ), 'PK' => __( 'Pakistan', 'all-in-one-seo-pack' ), 'PS' => __( 'Palestine', 'all-in-one-seo-pack' ), 'PA' => __( 'Panama', 'all-in-one-seo-pack' ), 'PG' => __( 'Papua New Guinea', 'all-in-one-seo-pack' ), 'PY' => __( 'Paraguay', 'all-in-one-seo-pack' ), 'PE' => __( 'Peru', 'all-in-one-seo-pack' ), 'PH' => __( 'Philippines', 'all-in-one-seo-pack' ), 'PN' => __( 'Pitcairn Islands', 'all-in-one-seo-pack' ), 'PL' => __( 'Poland', 'all-in-one-seo-pack' ), 'PT' => __( 'Portugal', 'all-in-one-seo-pack' ), 'PR' => __( 'Puerto Rico', 'all-in-one-seo-pack' ), 'QA' => __( 'Qatar', 'all-in-one-seo-pack' ), 'RO' => __( 'Romania', 'all-in-one-seo-pack' ), 'RU' => __( 'Russia', 'all-in-one-seo-pack' ), 'RW' => __( 'Rwanda', 'all-in-one-seo-pack' ), 'WS' => __( 'Samoa', 'all-in-one-seo-pack' ), 'SM' => __( 'San Marino', 'all-in-one-seo-pack' ), 'ST' => __( 'São Tomé & Príncipe', 'all-in-one-seo-pack' ), 'SA' => __( 'Saudi Arabia', 'all-in-one-seo-pack' ), 'SN' => __( 'Senegal', 'all-in-one-seo-pack' ), 'RS' => __( 'Serbia', 'all-in-one-seo-pack' ), 'SC' => __( 'Seychelles', 'all-in-one-seo-pack' ), 'SL' => __( 'Sierra Leone', 'all-in-one-seo-pack' ), 'SG' => __( 'Singapore', 'all-in-one-seo-pack' ), 'SK' => __( 'Slovakia', 'all-in-one-seo-pack' ), 'SI' => __( 'Slovenia', 'all-in-one-seo-pack' ), 'SB' => __( 'Solomon Islands', 'all-in-one-seo-pack' ), 'SO' => __( 'Somalia', 'all-in-one-seo-pack' ), 'ZA' => __( 'South Africa', 'all-in-one-seo-pack' ), 'KR' => __( 'South Korea', 'all-in-one-seo-pack' ), 'ES' => __( 'Spain', 'all-in-one-seo-pack' ), 'LK' => __( 'Sri Lanka', 'all-in-one-seo-pack' ), 'SH' => __( 'St. Helena', 'all-in-one-seo-pack' ), 'VC' => __( 'St. Vincent & Grenadines', 'all-in-one-seo-pack' ), 'SR' => __( 'Suriname', 'all-in-one-seo-pack' ), 'SE' => __( 'Sweden', 'all-in-one-seo-pack' ), 'CH' => __( 'Switzerland', 'all-in-one-seo-pack' ), 'TW' => __( 'Taiwan', 'all-in-one-seo-pack' ), 'TJ' => __( 'Tajikistan', 'all-in-one-seo-pack' ), 'TZ' => __( 'Tanzania', 'all-in-one-seo-pack' ), 'TH' => __( 'Thailand', 'all-in-one-seo-pack' ), 'TL' => __( 'Timor-Leste', 'all-in-one-seo-pack' ), 'TG' => __( 'Togo', 'all-in-one-seo-pack' ), 'TO' => __( 'Tonga', 'all-in-one-seo-pack' ), 'TT' => __( 'Trinidad & Tobago', 'all-in-one-seo-pack' ), 'TN' => __( 'Tunisia', 'all-in-one-seo-pack' ), 'TR' => __( 'Turkey', 'all-in-one-seo-pack' ), 'TM' => __( 'Turkmenistan', 'all-in-one-seo-pack' ), 'VI' => __( 'U.S. Virgin Islands', 'all-in-one-seo-pack' ), 'UG' => __( 'Uganda', 'all-in-one-seo-pack' ), 'UA' => __( 'Ukraine', 'all-in-one-seo-pack' ), 'AE' => __( 'United Arab Emirates', 'all-in-one-seo-pack' ), 'GB' => __( 'United Kingdom', 'all-in-one-seo-pack' ), 'US' => __( 'United States', 'all-in-one-seo-pack' ), 'UY' => __( 'Uruguay', 'all-in-one-seo-pack' ), 'UZ' => __( 'Uzbekistan', 'all-in-one-seo-pack' ), 'VU' => __( 'Vanuatu', 'all-in-one-seo-pack' ), 'VE' => __( 'Venezuela', 'all-in-one-seo-pack' ), 'VN' => __( 'Vietnam', 'all-in-one-seo-pack' ), 'ZM' => __( 'Zambia', 'all-in-one-seo-pack' ), 'ZW' => __( 'Zimbabwe', 'all-in-one-seo-pack' ) ]; return $countries; } /** * Returns the list of languages. * * @since 4.7.7.1 * * @return array The list of languages. */ private function getLanguages() { $languages = [ 'ca' => __( 'Catalan', 'all-in-one-seo-pack' ), 'da' => __( 'Danish', 'all-in-one-seo-pack' ), 'nl' => __( 'Dutch', 'all-in-one-seo-pack' ), 'en' => __( 'English', 'all-in-one-seo-pack' ), 'fr' => __( 'French', 'all-in-one-seo-pack' ), 'de' => __( 'German', 'all-in-one-seo-pack' ), 'id' => __( 'Indonesian', 'all-in-one-seo-pack' ), 'it' => __( 'Italian', 'all-in-one-seo-pack' ), 'no' => __( 'Norwegian', 'all-in-one-seo-pack' ), 'pt' => __( 'Portuguese', 'all-in-one-seo-pack' ), 'ro' => __( 'Romanian', 'all-in-one-seo-pack' ), 'es' => __( 'Spanish', 'all-in-one-seo-pack' ), 'sv' => __( 'Swedish', 'all-in-one-seo-pack' ), 'tr' => __( 'Turkish', 'all-in-one-seo-pack' ) ]; return $languages; } /** * Returns the list of search engines. * * @since 4.7.7.1 * * @return array The list of search engines. */ private function getSearchEngines() { $searchEngines = [ 'AF' => 'google.com.af', 'AL' => 'google.al', 'DZ' => 'google.dz', 'AS' => 'google.as', 'AD' => 'google.ad', 'AO' => 'google.it.ao', 'AI' => 'google.com.ai', 'AG' => 'google.com.ag', 'AR' => 'google.com.ar', 'AM' => 'google.am', 'AU' => 'google.com.au', 'AT' => 'google.at', 'AZ' => 'google.az', 'BS' => 'google.bs', 'BH' => 'google.com.bh', 'BD' => 'google.com.bd', 'BY' => 'google.com.by', 'BE' => 'google.be', 'BZ' => 'google.com.bz', 'BJ' => 'google.bj', 'BT' => 'google.bt', 'BO' => 'google.com.bo', 'BA' => 'google.ba', 'BW' => 'google.co.bw', 'BR' => 'google.com.br', 'VG' => 'google.vg', 'BN' => 'google.com.bn', 'BG' => 'google.bg', 'BF' => 'google.bf', 'BI' => 'google.bi', 'KH' => 'google.com.kh', 'CM' => 'google.cm', 'CA' => 'google.ca', 'CV' => 'google.cv', 'CF' => 'google.cf', 'TD' => 'google.td', 'CL' => 'google.cl', 'CO' => 'google.com.co', 'CG' => 'google.cg', 'CD' => 'google.cd', 'CK' => 'google.co.ck', 'CR' => 'google.co.cr', 'CI' => 'google.ci', 'HR' => 'google.hr', 'CU' => 'google.com.cu', 'CY' => 'google.com.cy', 'CZ' => 'google.cz', 'DK' => 'google.dk', 'DJ' => 'google.dj', 'DM' => 'google.dm', 'DO' => 'google.com.do', 'EC' => 'google.com.ec', 'EG' => 'google.com.eg', 'SV' => 'google.com.sv', 'EE' => 'google.ee', 'ET' => 'google.com.et', 'FJ' => 'google.com.fj', 'FI' => 'google.fi', 'FR' => 'google.fr', 'GA' => 'google.ga', 'GM' => 'google.gm', 'GE' => 'google.ge', 'DE' => 'google.de', 'GH' => 'google.com.gh', 'GI' => 'google.com.gi', 'GR' => 'google.gr', 'GL' => 'google.gl', 'GT' => 'google.com.gt', 'GG' => 'google.gg', 'GY' => 'google.gy', 'HT' => 'google.ht', 'HN' => 'google.hn', 'HK' => 'google.com.hk', 'HU' => 'google.hu', 'IS' => 'google.is', 'IN' => 'google.co.in', 'ID' => 'google.co.id', 'IQ' => 'google.iq', 'IE' => 'google.ie', 'IM' => 'google.co.im', 'IL' => 'google.co.il', 'IT' => 'google.it', 'JM' => 'google.com.jm', 'JP' => 'google.co.jp', 'JE' => 'google.co.je', 'JO' => 'google.jo', 'KZ' => 'google.kz', 'KE' => 'google.co.ke', 'KI' => 'google.ki', 'KW' => 'google.com.kw', 'KG' => 'google.com.kg', 'LA' => 'google.la', 'LV' => 'google.lv', 'LB' => 'google.com.lb', 'LS' => 'google.co.ls', 'LY' => 'google.com.ly', 'LI' => 'google.li', 'LT' => 'google.lt', 'LU' => 'google.lu', 'MG' => 'google.mg', 'MW' => 'google.mw', 'MY' => 'google.com.my', 'MV' => 'google.mv', 'ML' => 'google.ml', 'MT' => 'google.com.mt', 'MU' => 'google.mu', 'MX' => 'google.com.mx', 'FM' => 'google.fm', 'MD' => 'google.md', 'MN' => 'google.mn', 'ME' => 'google.me', 'MS' => 'google.ms', 'MA' => 'google.co.ma', 'MZ' => 'google.co.mz', 'MM' => 'google.com.mm', 'NA' => 'google.com.na', 'NR' => 'google.nr', 'NP' => 'google.com.np', 'NL' => 'google.nl', 'NZ' => 'google.co.nz', 'NI' => 'google.com.ni', 'NE' => 'google.ne', 'NG' => 'google.com.ng', 'NU' => 'google.nu', 'MK' => 'google.mk', 'NO' => 'google.no', 'OM' => 'google.com.om', 'PK' => 'google.com.pk', 'PS' => 'google.ps', 'PA' => 'google.com.pa', 'PG' => 'google.com.pg', 'PY' => 'google.com.py', 'PE' => 'google.com.pe', 'PH' => 'google.com.ph', 'PN' => 'google.pn', 'PL' => 'google.pl', 'PT' => 'google.pt', 'PR' => 'google.com.pr', 'QA' => 'google.com.qa', 'RO' => 'google.ro', 'RU' => 'google.ru', 'RW' => 'google.rw', 'WS' => 'google.as', 'SM' => 'google.sm', 'ST' => 'google.st', 'SA' => 'google.com.sa', 'SN' => 'google.sn', 'RS' => 'google.rs', 'SC' => 'google.sc', 'SL' => 'google.com.sl', 'SG' => 'google.com.sg', 'SK' => 'google.sk', 'SI' => 'google.si', 'SB' => 'google.com.sb', 'SO' => 'google.so', 'ZA' => 'google.co.za', 'KR' => 'google.co.kr', 'ES' => 'google.es', 'LK' => 'google.lk', 'SH' => 'google.sh', 'VC' => 'google.com.vc', 'SR' => 'google.sr', 'SE' => 'google.se', 'CH' => 'google.ch', 'TW' => 'google.com.tw', 'TJ' => 'google.com.tj', 'TZ' => 'google.co.tz', 'TH' => 'google.co.th', 'TL' => 'google.tl', 'TG' => 'google.tg', 'TO' => 'google.to', 'TT' => 'google.tt', 'TN' => 'google.tn', 'TR' => 'google.com.tr', 'TM' => 'google.tm', 'VI' => 'google.co.vi', 'UG' => 'google.co.ug', 'UA' => 'google.com.ua', 'AE' => 'google.ae', 'GB' => 'google.co.uk', 'US' => 'google.com', 'UY' => 'google.com.uy', 'UZ' => 'google.co.uz', 'VU' => 'google.vu', 'VE' => 'google.co.ve', 'VN' => 'google.com.vn', 'ZM' => 'google.co.zm', 'ZW' => 'google.co.zw' ]; return $searchEngines; } }app/Common/WritingAssistant/SeoBoost/Service.php000064400000014005150211766350015745 0ustar00doRequest( 'waAddNewReport', [ 'params' => [ 'keyword' => $keyword, 'country' => $country, 'language' => $language ] ] ); if ( is_wp_error( $reportRequest ) ) { return $reportRequest; } if ( empty( $reportRequest ) || empty( $reportRequest['status'] ) ) { return new \WP_Error( 'service-error', __( 'Empty response from service', 'all-in-one-seo-pack' ) ); } if ( 'success' !== $reportRequest['status'] ) { return new \WP_Error( 'service-error', $reportRequest['msg'] ); } return $reportRequest; } /** * Sends a post content to be analyzed. * * @since 4.7.4 * * @param string $title The title. * @param string $description The description. * @param string $content The content. * @param string $reportSlug The report slug. * @return array|\WP_Error The response. */ public function getContentAnalysis( $title, $description, $content, $reportSlug ) { return $this->doRequest( 'waAnalyzeContent', [ 'title' => $title, 'description' => $description, 'content' => $content, 'slug' => $reportSlug ] ); } /** * Gets the progress for a keyword. * * @since 4.7.4 * * @param string $uuid The uuid. * @return array|\WP_Error The progress. */ public function getProgressAndResult( $uuid ) { $response = $this->doRequest( 'waGetReport', [ 'slug' => $uuid ] ); if ( is_wp_error( $response ) ) { return $response; } if ( empty( $response ) ) { return new \WP_Error( 'empty-progress-and-result', __( 'Empty progress and result.', 'all-in-one-seo-pack' ) ); } return $response; } /** * Gets the user options. * * @since 4.7.4 * * @return array|\WP_Error The user options. */ public function getUserOptions() { return $this->doRequest( 'waGetUserOptions' ); } /** * Gets the user information. * * @since 4.7.4 * * @return array|\WP_Error The user information. */ public function getUserInfo() { return $this->doRequest( 'waGetUserInfo' ); } /** * Gets the access token. * * @since 4.7.4 * * @param string $authToken The auth token. * @return array|\WP_Error The response. */ public function getAccessToken( $authToken ) { return $this->doRequest( 'oauthaccess', [ 'token' => $authToken ] ); } /** * Refreshes the access token. * * @since 4.7.4 * * @return bool Was the token refreshed? */ private function refreshAccessToken() { $newAccessToken = $this->doRequest( 'waRefreshAccessToken' ); if ( is_wp_error( $newAccessToken ) || 'success' !== $newAccessToken['status'] ) { aioseo()->writingAssistant->seoBoost->setAccessToken( '' ); return false; } aioseo()->writingAssistant->seoBoost->setAccessToken( $newAccessToken['token'] ); return true; } /** * Sends a POST request to the microservice. * * @since 4.7.4 * * @param string $path The path. * @param array $requestBody The request body. * @return array|\WP_Error Returns the response body or WP_Error if the request failed. */ private function doRequest( $path, $requestBody = [] ) { // Prevent API requests if no access token is present. if ( 'oauthaccess' !== $path && // Except if we're getting the access token. empty( aioseo()->writingAssistant->seoBoost->getAccessToken() ) ) { return new \WP_Error( 'service-error', __( 'Missing access token', 'all-in-one-seo-pack' ) ); } $requestData = [ 'headers' => [ 'X-SeoBoost-Access-Token' => aioseo()->writingAssistant->seoBoost->getAccessToken(), 'X-SeoBoost-Domain' => aioseo()->helpers->getMultiSiteDomain(), 'Content-Type' => 'application/json' ], 'timeout' => 60, 'method' => 'GET' ]; if ( ! empty( $requestBody ) ) { $requestData['method'] = 'POST'; $requestData['body'] = wp_json_encode( $requestBody ); } $path = trailingslashit( $this->getUrl() ) . trailingslashit( $path ); $response = wp_remote_request( $path, $requestData ); $responseBody = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! $responseBody ) { $response = new \WP_Error( 'service-failed', __( 'Error in the SeoBoost service. Please contact support.', 'all-in-one-seo-pack' ) ); } if ( is_wp_error( $response ) ) { return $response; } // Refresh access token if expired and redo the request. if ( isset( $responseBody['error'] ) && 'invalid-access-token' === $responseBody['error'] ) { if ( $this->refreshAccessToken() ) { return $this->doRequest( $path, $requestBody ); } } return $responseBody; } /** * Returns the URL for the Writing Assistant service. * * @since 4.7.4 * * @return string The URL. */ public function getUrl() { $url = $this->baseUrl; if ( defined( 'AIOSEO_WRITING_ASSISTANT_SERVICE_URL' ) ) { $url = AIOSEO_WRITING_ASSISTANT_SERVICE_URL; } return $url; } /** * Gets the report history. * * @since 4.7.4 * * @return array|\WP_Error */ public function getReportHistory() { return $this->doRequest( 'waGetReportHistory' ); } }app/Common/WritingAssistant/WritingAssistant.php000064400000001102150211766350016117 0ustar00helpers = new Utils\Helpers(); $this->seoBoost = new SeoBoost\SeoBoost(); } }app/Common/Breadcrumbs/Breadcrumbs.php000064400000051072150211766350013762 0ustar00frontend = new Frontend(); $this->shortcode = new Shortcode(); $this->block = new Block(); add_action( 'widgets_init', [ $this, 'registerWidget' ] ); // Init Tags class later as we need post types registered. add_action( 'init', [ $this, 'init' ], 50 ); } public function init() { $this->tags = new Tags(); } /** * Helper to add crumbs on the breadcrumb array. * * @since 4.1.1 * * @param array $crumbs A single crumb or an array of crumbs. * @return void */ public function addCrumbs( $crumbs ) { if ( empty( $crumbs ) || ! is_array( $crumbs ) ) { return; } // If it's a single crumb put it inside an array to merge. if ( isset( $crumbs['label'] ) ) { $crumbs = [ $crumbs ]; } $this->breadcrumbs = array_merge( $this->breadcrumbs, $crumbs ); } /** * Builds a crumb array based on a type and a reference. * * @since 4.1.1 * * @param string $type The type of breadcrumb ( post, single, page, category, tag, taxonomy, postTypeArchive, date, * author, search, notFound, blog ). * @param mixed $reference The reference can be an object ( WP_Post | WP_Term | WP_Post_Type | WP_User ), an array, an int or a string. * @param array $paged A reference for a paged crumb. * @return array An array of breadcrumbs with their label, link, type and reference. */ public function buildBreadcrumbs( $type, $reference, $paged = [] ) { // Clear the breadcrumb array and build a new one. $this->breadcrumbs = []; // Add breadcrumb prefix. $this->addCrumbs( $this->getPrefixCrumb( $type, $reference ) ); // Set a home page in the beginning of the breadcrumb. $this->addCrumbs( $this->maybeGetHomePageCrumb( $type, $reference ) ); // Woocommerce shop page support. $this->addCrumbs( $this->maybeGetWooCommerceShopCrumb() ); // Blog home. if ( aioseo()->options->breadcrumbs->showBlogHome && in_array( $type, [ 'category', 'tag', 'post', 'author', 'date' ], true ) ) { $this->addCrumbs( $this->getBlogCrumb() ); } switch ( $type ) { case 'post': case 'single': $this->addCrumbs( $this->getPostArchiveCrumb( $reference ) ); $this->addCrumbs( $this->getPostTaxonomyCrumbs( $reference ) ); $this->addCrumbs( $this->getPostParentCrumbs( $reference ) ); $this->addCrumbs( $this->getPostCrumb( $reference ) ); break; case 'page': $this->addCrumbs( $this->getPostParentCrumbs( $reference, 'page' ) ); $this->addCrumbs( $this->getPostCrumb( $reference, 'page' ) ); break; case 'category': case 'tag': case 'taxonomy': $this->addCrumbs( $this->getTermTaxonomyParentCrumbs( $reference ) ); $this->addCrumbs( $this->getTermTaxonomyCrumb( $reference ) ); break; case 'postTypeArchive': $this->addCrumbs( $this->getPostTypeArchiveCrumb( $reference ) ); break; case 'date': $this->addCrumbs( $this->getDateCrumb( $reference ) ); break; case 'author': $this->addCrumbs( $this->getAuthorCrumb( $reference ) ); break; case 'blog': $this->addCrumbs( $this->getBlogCrumb() ); break; case 'search': $this->addCrumbs( $this->getSearchCrumb( $reference ) ); break; case 'notFound': $this->addCrumbs( $this->getNotFoundCrumb() ); break; case 'preview': $this->addCrumbs( $this->getPreviewCrumb( $reference ) ); break; case 'wcProduct': $this->addCrumbs( $this->getPostTaxonomyCrumbs( $reference ) ); $this->addCrumbs( $this->getPostParentCrumbs( $reference ) ); $this->addCrumbs( $this->getPostCrumb( $reference ) ); break; case 'buddypress': $this->addCrumbs( aioseo()->standalone->buddyPress->component->getCrumbs() ); break; } // Paged crumb. if ( ! empty( $paged['paged'] ) ) { $this->addCrumbs( $this->getPagedCrumb( $paged ) ); } // Maybe remove the last crumb. if ( ! $this->showCurrentItem( $type, $reference ) ) { array_pop( $this->breadcrumbs ); } // Remove empty crumbs. $this->breadcrumbs = array_filter( $this->breadcrumbs ); return $this->breadcrumbs; } /** * Gets the prefix crumb. * * @since 4.1.1 * * @param string $type The type of breadcrumb. * @param mixed $reference The breadcrumb reference. * @return array A crumb. */ public function getPrefixCrumb( $type, $reference ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable if ( 0 === strlen( aioseo()->options->breadcrumbs->breadcrumbPrefix ) ) { return []; } return $this->makeCrumb( aioseo()->options->breadcrumbs->breadcrumbPrefix, '', 'prefix' ); } /** * Gets the 404 crumb. * * @since 4.1.1 * * @return array A crumb. */ public function getNotFoundCrumb() { return $this->makeCrumb( aioseo()->options->breadcrumbs->errorFormat404, '', 'notFound' ); } /** * Gets the search crumb. * * @since 4.1.1 * * @param string $searchQuery The search query for reference. * @return array A crumb. */ public function getSearchCrumb( $searchQuery ) { return $this->makeCrumb( aioseo()->options->breadcrumbs->searchResultFormat, get_search_link( $searchQuery ), 'search', $searchQuery ); } /** * Gets the preview crumb. * * @since 4.1.5 * * @param string $label The preview label. * @return array A crumb. */ public function getPreviewCrumb( $label ) { return $this->makeCrumb( $label, '', 'preview' ); } /** * Gets the post type archive crumb. * * @since 4.1.1 * * @param \WP_Post_Type $postType The post type object for reference. * @return array A crumb. */ public function getPostTypeArchiveCrumb( $postType ) { return $this->makeCrumb( aioseo()->options->breadcrumbs->archiveFormat, get_post_type_archive_link( $postType->name ), 'postTypeArchive', $postType ); } /** * Gets a post crumb. * * @since 4.1.1 * * @param \WP_Post $post A post object for reference. * @param string $type The breadcrumb type. * @param string $subType The breadcrumb subType. * @return array A crumb. */ public function getPostCrumb( $post, $type = 'single', $subType = '' ) { return $this->makeCrumb( get_the_title( $post ), get_permalink( $post ), $type, $post, $subType ); } /** * Gets the term crumb. * * @since 4.1.1 * * @param \WP_Term $term The term object for reference. * @param string $subType The breadcrumb subType. * @return array A crumb. */ public function getTermTaxonomyCrumb( $term, $subType = '' ) { return $this->makeCrumb( $term->name, get_term_link( $term ), 'taxonomy', $term, $subType ); } /** * Gets the paged crumb. * * @since 4.1.1 * * @param array $reference The paged array for reference. * @return array A crumb. */ public function getPagedCrumb( $reference ) { return $this->makeCrumb( sprintf( '%1$s %2$s', __( 'Page', 'all-in-one-seo-pack' ), $reference['paged'] ), $reference['link'], 'paged', $reference ); } /** * Gets the author crumb. * * @since 4.1.1 * * @param \WP_User $wpUser A WP_User object. * @return array A crumb. */ public function getAuthorCrumb( $wpUser ) { return $this->makeCrumb( $wpUser->display_name, get_author_posts_url( $wpUser->ID ), 'author', $wpUser ); } /** * Gets the date crumb. * * @since 4.1.1 * * @param array $reference An array of year, month and day values. * @return array A crumb. */ public function getDateCrumb( $reference ) { $dateCrumb = []; $addMonth = false; $addYear = false; if ( ! empty( $reference['day'] ) ) { $addMonth = true; $addYear = true; $dateCrumb[] = $this->makeCrumb( zeroise( (int) $reference['day'], 2 ), get_day_link( $reference['year'], $reference['month'], $reference['day'] ), 'day', $reference['day'] ); } if ( ! empty( $reference['month'] ) || $addMonth ) { $addYear = true; $dateCrumb[] = $this->makeCrumb( zeroise( (int) $reference['month'], 2 ), get_month_link( $reference['year'], $reference['month'] ), 'month', $reference['month'] ); } if ( ! empty( $reference['year'] ) || $addYear ) { $dateCrumb[] = $this->makeCrumb( $reference['year'], get_year_link( $reference['year'] ), 'year', $reference['year'] ); } return array_reverse( $dateCrumb ); } /** * Gets an array of crumbs parents for the term. * * @since 4.1.1 * * @param \WP_Term $term A WP_Term object. * @return array An array of parent crumbs. */ public function getTermTaxonomyParentCrumbs( $term ) { $crumbs = []; $termHierarchy = $this->getTermHierarchy( $term->term_id, $term->taxonomy ); if ( ! empty( $termHierarchy ) ) { foreach ( $termHierarchy as $parentTermId ) { $parentTerm = aioseo()->helpers->getTerm( $parentTermId, $term->taxonomy ); $crumbs[] = $this->getTermTaxonomyCrumb( $parentTerm, 'parent' ); } } return $crumbs; } /** * Helper function to create a standard crumb array. * * @since 4.1.1 * * @param string $label The crumb label. * @param string $link The crumb url. * @param null $type The crumb type. * @param null $reference The crumb reference. * @param null $subType The crumb subType ( single/parent ). * @return array A crumb array. */ public function makeCrumb( $label, $link = '', $type = null, $reference = null, $subType = null ) { return [ 'label' => $label, 'link' => $link, 'type' => $type, 'subType' => $subType, 'reference' => $reference ]; } /** * Gets a post archive crumb if it's post type has archives. * * @since 4.1.1 * * @param int|\WP_Post $post An ID or a WP_Post object. * @return array A crumb. */ public function getPostArchiveCrumb( $post ) { $postType = get_post_type_object( get_post_type( $post ) ); if ( ! $postType || ! $postType->has_archive ) { return []; } return $this->makeCrumb( $postType->labels->name, get_post_type_archive_link( $postType->name ), 'postTypeArchive', $postType ); } /** * Gets a post's taxonomy crumbs. * * @since 4.1.1 * * @param int|\WP_Post $post An ID or a WP_Post object. * @param null $taxonomy A taxonomy to use. If none is provided the first one with terms selected will be used. * @return array An array of term crumbs. */ public function getPostTaxonomyCrumbs( $post, $taxonomy = null ) { $crumbs = []; if ( $taxonomy && ! is_array( $taxonomy ) ) { $taxonomy = [ $taxonomy ]; } $termHierarchy = $this->getPostTaxTermHierarchy( $post, $taxonomy ); if ( ! empty( $termHierarchy['terms'] ) ) { foreach ( $termHierarchy['terms'] as $termId ) { $term = aioseo()->helpers->getTerm( $termId, $termHierarchy['taxonomy'] ); $crumbs[] = $this->makeCrumb( $term->name, get_term_link( $term, $termHierarchy['taxonomy'] ), 'taxonomy', $term, 'parent' ); } } return $crumbs; } /** * Gets the post's parent crumbs. * * @since 4.1.1 * * @param int|\WP_Post $post An ID or a WP_Post object. * @param string $type The crumb type. * @return array An array of the post parent crumbs. */ public function getPostParentCrumbs( $post, $type = 'single' ) { $crumbs = []; if ( ! is_post_type_hierarchical( get_post_type( $post ) ) ) { return $crumbs; } $postHierarchy = $this->getPostHierarchy( $post ); if ( ! empty( $postHierarchy ) ) { foreach ( $postHierarchy as $parentID ) { // Do not include the Home Page. if ( aioseo()->helpers->getHomePageId() === $parentID ) { continue; } $crumbs[] = $this->getPostCrumb( get_post( $parentID ), $type, 'parent' ); } } return $crumbs; } /** * Function to extend on pro for extra functionality. * * @since 4.1.1 * * @param string $type The type of breadcrumb. * @param mixed $reference The breadcrumb reference. * @return bool Show current item. */ public function showCurrentItem( $type = null, $reference = null ) { return apply_filters( 'aioseo_breadcrumbs_show_current_item', aioseo()->options->breadcrumbs->showCurrentItem, $type, $reference ); } /** * Gets a home page crumb. * * @since 4.1.1 * * @param string $type The type of breadcrumb. * @param mixed $reference The breadcrumb reference. * @return array|void The home crumb. */ public function maybeGetHomePageCrumb( $type = null, $reference = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable if ( aioseo()->options->breadcrumbs->homepageLink ) { return $this->getHomePageCrumb(); } } /** * Gets a home page crumb. * * @since 4.1.1 * * @return array The home crumb. */ public function getHomePageCrumb() { $homePageId = aioseo()->helpers->getHomePageId(); $label = ''; if ( $homePageId ) { $label = get_the_title( $homePageId ); } if ( 0 < strlen( aioseo()->options->breadcrumbs->homepageLabel ) ) { $label = aioseo()->options->breadcrumbs->homepageLabel; } // Label fallback. if ( empty( $label ) ) { $label = __( 'Home', 'all-in-one-seo-pack' ); } return $this->makeCrumb( $label, get_home_url(), 'homePage', aioseo()->helpers->getHomePage() ); } /** * Gets the blog crumb. * * @since 4.1.1 * * @return array The blog crumb. */ public function getBlogCrumb() { $crumb = []; $blogPage = aioseo()->helpers->getBlogPage(); if ( null !== $blogPage ) { $crumb = $this->makeCrumb( $blogPage->post_title, get_permalink( $blogPage ), 'blog', $blogPage ); } return $crumb; } /** * Maybe add the shop crumb to products and product categories. * * @since 4.5.5 * * @return array The shop crumb. */ public function maybeGetWooCommerceShopCrumb() { $crumb = []; if ( aioseo()->helpers->isWooCommerceShopPage() || aioseo()->helpers->isWooCommerceProductPage() || aioseo()->helpers->isWooCommerceTaxonomyPage() ) { $crumb = $this->getWooCommerceShopCrumb(); } return $crumb; } /** * Gets the shop crumb. * @see WC_Breadcrumb::prepend_shop_page() * * @since 4.5.5 * * @return array The shop crumb. */ public function getWooCommerceShopCrumb() { $crumb = []; if ( ! function_exists( 'wc_get_page_id' ) || apply_filters( 'aioseo_woocommerce_breadcrumb_hide_shop', false ) ) { return $crumb; } $shopPageId = wc_get_page_id( 'shop' ); $shopPage = get_post( $shopPageId ); // WC checks if the permalink contains the shop page in the URI, but we prefer to // always show the shop page as the first crumb if it exists and it's not the home page. if ( $shopPageId && $shopPage && aioseo()->helpers->getHomePageId() !== $shopPageId ) { $crumb = $this->makeCrumb( get_the_title( $shopPage ), get_permalink( $shopPage ), 'wcShop' ); } return $crumb; } /** * Gets a post's term hierarchy for a list of taxonomies selecting the one that has a lengthier hierarchy. * * @since 4.1.1 * * @param int|\WP_Post $post An ID or a WP_Post object. * @param array $taxonomies An array of taxonomy names. * @param false $skipUnselectedTerms Allow unselected terms to be filtered out from the crumbs. * @return array An array of the taxonomy name + a term hierarchy. */ public function getPostTaxTermHierarchy( $post, $taxonomies = [], $skipUnselectedTerms = false ) { // Get all taxonomies attached to the post. if ( empty( $taxonomies ) ) { $taxonomies = get_object_taxonomies( get_post_type( $post ), 'objects' ); $taxonomies = wp_filter_object_list( $taxonomies, [ 'public' => true ], 'and', 'name' ); } foreach ( $taxonomies as $taxonomy ) { $primaryTerm = aioseo()->standalone->primaryTerm->getPrimaryTerm( $post->ID, $taxonomy ); $terms = wp_get_object_terms( $post->ID, $taxonomy, [ 'orderby' => 'term_id', 'order' => 'ASC', ] ); // Use the first taxonomy with terms. if ( empty( $terms ) || is_wp_error( $terms ) ) { continue; } // Determines the lengthier term hierarchy. $termHierarchy = []; foreach ( $terms as $term ) { // Gets our filtered ancestors. $ancestors = $this->getFilteredTermHierarchy( $term->term_id, $term->taxonomy, $skipUnselectedTerms ? $terms : [] ); // Merge the current term to be used in the breadcrumbs. $ancestors = array_merge( $ancestors, [ $term->term_id ] ); // If the current term is the primary term, use it. if ( is_a( $primaryTerm, 'WP_Term' ) && $primaryTerm->term_id === $term->term_id ) { $termHierarchy = $ancestors; break; } $termHierarchy = ( count( $termHierarchy ) < count( $ancestors ) ) ? $ancestors : $termHierarchy; } // Return a top to bottom hierarchy. return [ 'taxonomy' => $taxonomy, 'terms' => $termHierarchy ]; } return []; } /** * Filters a term's parent hierarchy against other terms. * * @since 4.1.1 * * @param int $termId A term id. * @param string $taxonomy The taxonomy name. * @param array $termsToFilterAgainst Terms to filter out of the hierarchy. * @return array The term's parent hierarchy. */ public function getFilteredTermHierarchy( $termId, $taxonomy, $termsToFilterAgainst = [] ) { $ancestors = $this->getTermHierarchy( $termId, $taxonomy ); // Keep only selected terms in the hierarchy. if ( ! empty( $termsToFilterAgainst ) ) { // If it's a WP_Term array make it a term_id array. if ( is_a( current( $termsToFilterAgainst ), 'WP_Term' ) ) { $termsToFilterAgainst = wp_list_pluck( $termsToFilterAgainst, 'term_id' ); } $ancestors = array_intersect( $ancestors, $termsToFilterAgainst ); } return $ancestors; } /** * Gets a term's parent hierarchy. * * @since 4.1.1 * * @param int $termId A term id. * @param string $taxonomy A taxonomy name. * @return array The term parent hierarchy. */ public function getTermHierarchy( $termId, $taxonomy ) { // Return a top to bottom hierarchy. return array_reverse( get_ancestors( $termId, $taxonomy, 'taxonomy' ) ); } /** * Gets a post's parent hierarchy. * * @since 4.1.1 * * @param int|\WP_Post $post An ID or a WP_Post object. * @return array The post parent hierarchy. */ public function getPostHierarchy( $post ) { $postId = ! empty( $post->ID ) ? $post->ID : $post; // Return a top to bottom hierarchy. return array_reverse( get_ancestors( $postId, '', 'post_type' ) ); } /** * Register our breadcrumb widget. * * @since 4.1.1 * * @return void */ public function registerWidget() { if ( aioseo()->helpers->canRegisterLegacyWidget( 'aioseo-breadcrumb-widget' ) ) { register_widget( 'AIOSEO\Plugin\Common\Breadcrumbs\Widget' ); } } } } namespace { // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } if ( ! function_exists( 'aioseo_breadcrumbs' ) ) { /** * Global function for breadcrumbs output. * * @since 4.1.1 * * @param boolean $echo Echo or return the output. * @return string|void The output. */ function aioseo_breadcrumbs( $echo = true ) { return aioseo()->breadcrumbs->frontend->display( $echo ); } } }app/Common/Breadcrumbs/Widget.php000064400000006410150211766350012750 0ustar00defaults = [ 'title' => '' ]; // Widget Slug. $widgetSlug = 'aioseo-breadcrumb-widget'; // Widget basics. $widgetOps = [ 'classname' => $widgetSlug, 'description' => esc_html__( 'Display the current page breadcrumb.', 'all-in-one-seo-pack' ), ]; // Widget controls. $controlOps = [ 'id_base' => $widgetSlug, ]; // Translators: 1 - The plugin short name ("AIOSEO"). $name = sprintf( esc_html__( '%1$s - Breadcrumbs', 'all-in-one-seo-pack' ), AIOSEO_PLUGIN_SHORT_NAME ); $name .= ' ' . esc_html__( '(legacy)', 'all-in-one-seo-pack' ); parent::__construct( $widgetSlug, $name, $widgetOps, $controlOps ); } /** * Widget callback. * * @since 4.1.1 * * @param array $args Widget args. * @param array $instance The widget instance options. * @return void */ public function widget( $args, $instance ) { // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped // Merge with defaults. $instance = wp_parse_args( (array) $instance, $this->defaults ); echo $args['before_widget']; // Title. if ( ! empty( $instance['title'] ) ) { echo $args['before_title']; echo apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ); echo $args['after_title']; } // If not being previewed in the Customizer maybe show the dummy preview. if ( ! is_customize_preview() && ( false !== strpos( wp_get_referer(), admin_url( 'widgets.php' ) ) || false !== strpos( wp_get_referer(), admin_url( 'customize.php' ) ) ) ) { aioseo()->breadcrumbs->frontend->preview(); } else { aioseo()->breadcrumbs->frontend->display(); } echo $args['after_widget']; // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Widget option update. * * @since 4.1.1 * * @param array $newInstance New instance options. * @param array $oldInstance Old instance options. * @return array Processed new instance options. */ public function update( $newInstance, $oldInstance ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $newInstance['title'] = wp_strip_all_tags( $newInstance['title'] ); return $newInstance; } /** * Widget options form. * * @since 4.1.1 * * @param array $instance The widget instance options. * @return void */ public function form( $instance ) { // Merge with defaults. $instance = wp_parse_args( (array) $instance, $this->defaults ); ?>

breadcrumbs->frontend->display( false ); } }app/Common/Breadcrumbs/Block.php000064400000007663150211766350012572 0ustar00register(); } /** * Registers the block. * * @since 4.1.1 * * @return void */ public function register() { aioseo()->blocks->registerBlock( 'aioseo/breadcrumbs', [ 'attributes' => [ 'primaryTerm' => [ 'type' => 'string', 'default' => null ] ], 'render_callback' => [ $this, 'render' ] ] ); } /** * Renders the block. * * @since 4.1.1 * * @param array $blockAttributes The block attributes. * @return string The output from the output buffering. */ public function render( $blockAttributes ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable // phpcs:disable HM.Security.ValidatedSanitizedInput.InputNotSanitized, HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended $postId = ! empty( $_GET['post_id'] ) ? (int) sanitize_text_field( wp_unslash( $_GET['post_id'] ) ) : false; // phpcs:enable if ( ! empty( $blockAttributes['primaryTerm'] ) ) { $this->primaryTerm = json_decode( $blockAttributes['primaryTerm'], true ); } if ( aioseo()->blocks->isRenderingBlockInEditor() && ! empty( $postId ) ) { add_filter( 'aioseo_post_primary_term', [ $this, 'changePrimaryTerm' ], 10, 2 ); add_filter( 'get_object_terms', [ $this, 'temporarilyAddTerm' ], 10, 3 ); $breadcrumbs = aioseo()->breadcrumbs->frontend->sideDisplay( false, 'post' === get_post_type( $postId ) ? 'post' : 'single', get_post( $postId ) ); remove_filter( 'aioseo_post_primary_term', [ $this, 'changePrimaryTerm' ], 10 ); remove_filter( 'get_object_terms', [ $this, 'temporarilyAddTerm' ], 10 ); if ( in_array( 'breadcrumbsEnable', aioseo()->internalOptions->deprecatedOptions, true ) && ! aioseo()->options->deprecated->breadcrumbs->enable ) { return '

' . sprintf( // Translators: 1 - The plugin short name ("AIOSEO"), 2 - Opening HTML link tag, 3 - Closing HTML link tag. __( 'Breadcrumbs are currently disabled, so this block will be rendered empty. You can enable %1$s\'s breadcrumb functionality under %2$sGeneral Settings > Breadcrumbs%3$s.', 'all-in-one-seo-pack' ), // phpcs:ignore Generic.Files.LineLength.MaxExceeded AIOSEO_PLUGIN_SHORT_NAME, '', '' ) . '

'; } return $breadcrumbs; } return aioseo()->breadcrumbs->frontend->display( false ); } /** * Temporarily adds the primary term to the list of terms. * * @since 4.3.6 * * @param array $terms The list of terms. * @param array $objectIds The object IDs. * @param array $taxonomies The taxonomies. * @return array The list of terms. */ public function temporarilyAddTerm( $terms, $objectIds, $taxonomies ) { $taxonomy = $taxonomies[0]; if ( empty( $this->primaryTerm ) || empty( $this->primaryTerm[ $taxonomy ] ) ) { return $terms; } $term = aioseo()->helpers->getTerm( $this->primaryTerm[ $taxonomy ] ); if ( is_a( $term, 'WP_Term' ) ) { $terms[] = $term; } return $terms; } /** * Changes the primary term. * * @since 4.3.6 * * @param \WP_Term $term The term object. * @param string $taxonomy The taxonomy name. * @return \WP_Term The term object. */ public function changePrimaryTerm( $term, $taxonomy ) { if ( empty( $this->primaryTerm ) || empty( $this->primaryTerm[ $taxonomy ] ) ) { return $term; } return aioseo()->helpers->getTerm( $this->primaryTerm[ $taxonomy ], $taxonomy ); } }app/Common/Breadcrumbs/Frontend.php000064400000020557150211766350013314 0ustar00breadcrumbs ) ) { return apply_filters( 'aioseo_breadcrumbs_trail', $this->breadcrumbs ); } $reference = get_queried_object(); $type = ''; if ( BuddyPressIntegration::isComponentPage() ) { $type = 'buddypress'; } if ( ! $type ) { // These types need the queried object for reference. if ( is_object( $reference ) ) { if ( is_single() ) { $type = 'single'; } if ( is_singular( 'post' ) ) { $type = 'post'; } if ( is_page() && ! is_front_page() ) { $type = 'page'; } if ( is_category() || is_tag() ) { $type = 'category'; } if ( is_tax() ) { $type = 'taxonomy'; } if ( is_post_type_archive() ) { $type = 'postTypeArchive'; } if ( is_author() ) { $type = 'author'; } if ( is_home() ) { $type = 'blog'; } // Support WC shop page. if ( aioseo()->helpers->isWooCommerceShopPage() ) { $type = 'wcShop'; } // Support WC products. if ( aioseo()->helpers->isWooCommerceProductPage() ) { $type = 'wcProduct'; } } if ( is_date() ) { $type = 'date'; $reference = [ 'year' => get_query_var( 'year' ), 'month' => get_query_var( 'monthnum' ), 'day' => get_query_var( 'day' ) ]; } if ( is_search() ) { $type = 'search'; $reference = htmlspecialchars( sanitize_text_field( get_search_query() ) ); } if ( is_404() ) { $type = 'notFound'; } } $paged = false; if ( is_paged() || ( is_singular() && 1 < get_query_var( 'page' ) ) ) { global $wp; $paged = [ 'paged' => get_query_var( 'paged' ) ? get_query_var( 'paged' ) : get_query_var( 'page' ), 'link' => home_url( $wp->request ) ]; } return apply_filters( 'aioseo_breadcrumbs_trail', aioseo()->breadcrumbs->buildBreadcrumbs( $type, $reference, $paged ) ); } /** * Helper function to display breadcrumbs for a specific page. * * @since 4.1.1 * * @param bool $echo Print out the breadcrumb. * @param string $type The type for the breadcrumb. * @param string $reference A reference to be used for rendering the breadcrumb. * @return string|void A html breadcrumb. */ public function sideDisplay( $echo = true, $type = '', $reference = '' ) { // Save previously built breadcrumbs. $previousCrumbs = $this->breadcrumbs; // Build and run the sideDisplay. $this->breadcrumbs = aioseo()->breadcrumbs->buildBreadcrumbs( $type, $reference ); $sideDisplay = $this->display( $echo ); // Restore previously built breadcrumbs. $this->breadcrumbs = $previousCrumbs; return $sideDisplay; } /** * Display a generic breadcrumb preview. * * @since 4.1.5 * * @param bool $echo Print out the breadcrumb. * @param string $label The preview crumb label. * @return string|void A html breadcrumb. */ public function preview( $echo = true, $label = '' ) { // Translators: "Crumb" refers to a part of the breadcrumb trail. $label = empty( $label ) ? __( 'Sample Crumb', 'all-in-one-seo-pack' ) : $label; return $this->sideDisplay( $echo, 'preview', $label ); } /** * Display the breadcrumb in the frontend. * * @since 4.1.1 * * @param bool $echo Print out the breadcrumb. * @return string|void A html breadcrumb. */ public function display( $echo = true ) { if ( in_array( 'breadcrumbsEnable', aioseo()->internalOptions->deprecatedOptions, true ) && ! aioseo()->options->deprecated->breadcrumbs->enable ) { return; } if ( ! apply_filters( 'aioseo_breadcrumbs_output', true ) ) { return; } // We can only run after this action because we need all post types loaded. if ( ! did_action( 'init' ) ) { return; } $breadcrumbs = $this->getBreadcrumbs(); if ( empty( $breadcrumbs ) ) { return; } $breadcrumbsCount = count( $breadcrumbs ); $display = '
'; foreach ( $breadcrumbs as $breadcrumb ) { --$breadcrumbsCount; $breadcrumbDisplay = $this->breadcrumbToDisplay( $breadcrumb ); // Strip link from Last crumb. if ( 0 === $breadcrumbsCount && aioseo()->breadcrumbs->showCurrentItem() && ! $this->linkCurrentItem() && 'default' === $breadcrumbDisplay['templateType'] ) { $breadcrumbDisplay['template'] = $this->stripLink( $breadcrumbDisplay['template'] ); } $display .= $breadcrumbDisplay['template']; if ( 0 < $breadcrumbsCount ) { $display .= $this->getSeparator(); } } $display .= '
'; // Final security cleaning. $display = wp_kses_post( $display ); if ( $echo ) { echo $display; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } return $display; } /** * Turns a crumb array into a rendered html crumb. * * @since 4.1.1 * * @param array $item The crumb array. * @return string|void The crumb html. */ protected function breadcrumbToDisplay( $item ) { $templateItem = $this->getCrumbTemplate( $item ); if ( empty( $templateItem['template'] ) ) { return; } // Do tags. $templateItem['template'] = aioseo()->breadcrumbs->tags->replaceTags( $templateItem['template'], $item ); $templateItem['template'] = preg_replace_callback( '/>(?![^<]*>)(?![^>]*")([^<]*?)>/', function ( $matches ) { return '>' . $matches[1] . '>'; }, htmlentities( $templateItem['template'] ) ); // Restore html. $templateItem['template'] = aioseo()->helpers->decodeHtmlEntities( $templateItem['template'] ); // Remove html link if it comes back from the template but we passed no links to it. if ( empty( $item['link'] ) ) { $templateItem['template'] = $this->stripLink( $templateItem['template'] ); } // Allow shortcodes to run in the final html. $templateItem['template'] = do_shortcode( $templateItem['template'] ); return $templateItem; } /** * Helper function to get a crumb's template. * * @since 4.1.1 * * @param array $crumb The crumb array. * @return string The html template. */ protected function getTemplate( $crumb ) { return $this->getDefaultTemplate( $crumb ); } /** * Helper function to get a crumb's template. * * @since 4.1.1 * * @param array $crumb The crumb array. * @return array The template type and html. */ protected function getCrumbTemplate( $crumb ) { return [ 'templateType' => 'default', 'template' => $this->getTemplate( $crumb ) ]; } /** * Default html template. * * @since 4.1.1 * * @param string $type The crumb's type. * @param mixed $reference The crumb's reference. * @return string The default crumb template. */ public function getDefaultTemplate( $type = '', $reference = '' ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable return <<