Commit realizado el 12:13:52 08-04-2024
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Activation_Notice.
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2020 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Google\Web_Stories\Admin;
|
||||
|
||||
use Google\Web_Stories\Assets;
|
||||
use Google\Web_Stories\Infrastructure\PluginActivationAware;
|
||||
use Google\Web_Stories\Infrastructure\PluginDeactivationAware;
|
||||
use Google\Web_Stories\Infrastructure\PluginUninstallAware;
|
||||
use Google\Web_Stories\Infrastructure\Registerable;
|
||||
use Google\Web_Stories\Infrastructure\Service as ServiceInterface;
|
||||
use Google\Web_Stories\Story_Post_Type;
|
||||
use Google\Web_Stories\Tracking;
|
||||
|
||||
/**
|
||||
* Class Activation_Notice.
|
||||
*/
|
||||
class Activation_Notice implements ServiceInterface, Registerable, PluginActivationAware, PluginDeactivationAware, PluginUninstallAware {
|
||||
|
||||
/**
|
||||
* Script handle.
|
||||
*/
|
||||
public const SCRIPT_HANDLE = 'web-stories-activation-notice';
|
||||
|
||||
/**
|
||||
* Option name.
|
||||
*/
|
||||
public const OPTION_SHOW_ACTIVATION_NOTICE = 'web_stories_show_activation_notice';
|
||||
|
||||
/**
|
||||
* Assets instance.
|
||||
*
|
||||
* @var Assets Assets instance.
|
||||
*/
|
||||
private Assets $assets;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param Assets $assets Assets instance.
|
||||
*/
|
||||
public function __construct( Assets $assets ) {
|
||||
$this->assets = $assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the plugin activation notice.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register(): void {
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
add_action( 'admin_notices', [ $this, 'render_notice' ] );
|
||||
add_action( 'network_admin_notices', [ $this, 'render_notice' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on plugin activation.
|
||||
*
|
||||
* @since 1.13.0
|
||||
*
|
||||
* @param bool $network_wide Whether the activation was done network-wide.
|
||||
*/
|
||||
public function on_plugin_activation( bool $network_wide ): void {
|
||||
$this->set_activation_flag( $network_wide );
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on plugin deactivation.
|
||||
*
|
||||
* @since 1.13.0
|
||||
*
|
||||
* @param bool $network_wide Whether the deactivation was done network-wide.
|
||||
*/
|
||||
public function on_plugin_deactivation( bool $network_wide ): void {
|
||||
$this->delete_activation_flag( $network_wide );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues assets for the plugin activation notice.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $hook_suffix The current admin page.
|
||||
*/
|
||||
public function enqueue_assets( string $hook_suffix ): void {
|
||||
if ( ! $this->is_plugins_page( $hook_suffix ) || ! $this->get_activation_flag( is_network_admin() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the default WordPress "Plugin Activated" notice from rendering.
|
||||
*
|
||||
* @link https://github.com/WordPress/WordPress/blob/e1996633228749cdc2d92bc04cc535d45367bfa4/wp-admin/plugins.php#L569-L570
|
||||
*/
|
||||
unset( $_GET['activate'] ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.VIP.SuperGlobalInputUsage
|
||||
|
||||
$this->assets->enqueue_style( Google_Fonts::SCRIPT_HANDLE );
|
||||
|
||||
$this->assets->enqueue_script_asset( self::SCRIPT_HANDLE, [ Tracking::SCRIPT_HANDLE ] );
|
||||
|
||||
wp_localize_script(
|
||||
self::SCRIPT_HANDLE,
|
||||
'webStoriesActivationSettings',
|
||||
$this->get_script_settings()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the plugin activation notice.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function render_notice(): void {
|
||||
global $hook_suffix;
|
||||
|
||||
if ( ! $this->is_plugins_page( $hook_suffix ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$network_wide = is_network_admin();
|
||||
$flag = $this->get_activation_flag( $network_wide );
|
||||
|
||||
if ( ! $flag ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Unset the flag so that the notice only shows once.
|
||||
$this->delete_activation_flag( $network_wide );
|
||||
|
||||
require_once WEBSTORIES_PLUGIN_DIR_PATH . 'includes/templates/admin/activation-notice.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the flag that the plugin has just been uninstalled.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*/
|
||||
public function on_plugin_uninstall(): void {
|
||||
if ( is_multisite() ) {
|
||||
delete_site_option( self::OPTION_SHOW_ACTIVATION_NOTICE );
|
||||
}
|
||||
delete_option( self::OPTION_SHOW_ACTIVATION_NOTICE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns script settings as an array.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array{id: string, config: array<string, bool|string>,publicPath: string} Script settings.
|
||||
*/
|
||||
protected function get_script_settings(): array {
|
||||
$new_story_url = admin_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'post_type' => Story_Post_Type::POST_TYPE_SLUG,
|
||||
],
|
||||
'post-new.php'
|
||||
)
|
||||
);
|
||||
|
||||
$dashboard_url = admin_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'post_type' => Story_Post_Type::POST_TYPE_SLUG,
|
||||
'page' => 'stories-dashboard',
|
||||
],
|
||||
'edit.php'
|
||||
)
|
||||
);
|
||||
|
||||
$demo_story_url = admin_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'post_type' => Story_Post_Type::POST_TYPE_SLUG,
|
||||
'web-stories-demo' => 1,
|
||||
],
|
||||
'post-new.php'
|
||||
)
|
||||
);
|
||||
|
||||
return [
|
||||
'id' => 'web-stories-plugin-activation-notice',
|
||||
'config' => [
|
||||
'isRTL' => is_rtl(),
|
||||
'cdnURL' => trailingslashit( WEBSTORIES_CDN_URL ),
|
||||
'demoStoryURL' => $demo_story_url,
|
||||
'newStoryURL' => $new_story_url,
|
||||
'dashboardURL' => $dashboard_url,
|
||||
],
|
||||
'publicPath' => $this->assets->get_base_url( 'assets/js/' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether we're currently on the Plugins page or not.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param mixed $hook_suffix Current hook_suffix.
|
||||
* @return bool Whether we're on the Plugins page.
|
||||
*/
|
||||
protected function is_plugins_page( $hook_suffix ): bool {
|
||||
return ( ! empty( $hook_suffix ) && 'plugins.php' === $hook_suffix );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flag that the plugin has just been activated.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
|
||||
*
|
||||
* @since 1.13.0
|
||||
*
|
||||
* @param bool $network_wide Whether the plugin is being activated network-wide.
|
||||
*/
|
||||
protected function set_activation_flag( bool $network_wide = false ): bool {
|
||||
if ( $network_wide ) {
|
||||
return update_site_option( self::OPTION_SHOW_ACTIVATION_NOTICE, '1' );
|
||||
}
|
||||
|
||||
return update_option( self::OPTION_SHOW_ACTIVATION_NOTICE, '1', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flag that the plugin has just been activated.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
|
||||
*
|
||||
* @since 1.13.0
|
||||
*
|
||||
* @param bool $network_wide Whether to check the flag network-wide.
|
||||
* @return bool True if just activated, false otherwise.
|
||||
*/
|
||||
protected function get_activation_flag( bool $network_wide = false ): bool {
|
||||
if ( $network_wide ) {
|
||||
return (bool) get_site_option( self::OPTION_SHOW_ACTIVATION_NOTICE, false );
|
||||
}
|
||||
|
||||
return (bool) get_option( self::OPTION_SHOW_ACTIVATION_NOTICE, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the flag that the plugin has just been activated.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
|
||||
*
|
||||
* @since 1.13.0
|
||||
*
|
||||
* @param bool $network_wide Whether the plugin is being deactivated network-wide.
|
||||
* @return bool True if flag deletion is successful, false otherwise.
|
||||
*/
|
||||
protected function delete_activation_flag( bool $network_wide = false ): bool {
|
||||
if ( $network_wide ) {
|
||||
return delete_site_option( self::OPTION_SHOW_ACTIVATION_NOTICE );
|
||||
}
|
||||
|
||||
return delete_option( self::OPTION_SHOW_ACTIVATION_NOTICE );
|
||||
}
|
||||
}
|
271
wp-content/plugins/web-stories/includes/Admin/Admin.php
Normal file
271
wp-content/plugins/web-stories/includes/Admin/Admin.php
Normal file
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin class.
|
||||
*
|
||||
* Responsible for WordPress admin integration.
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2020 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Google\Web_Stories\Admin;
|
||||
|
||||
use Google\Web_Stories\Context;
|
||||
use Google\Web_Stories\Model\Story;
|
||||
use Google\Web_Stories\Renderer\Story\Image;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
use Google\Web_Stories\Settings;
|
||||
use Google\Web_Stories\Story_Post_Type;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Admin class.
|
||||
*/
|
||||
class Admin extends Service_Base {
|
||||
|
||||
/**
|
||||
* Settings instance.
|
||||
*
|
||||
* @var Settings Settings instance.
|
||||
*/
|
||||
private Settings $settings;
|
||||
|
||||
/**
|
||||
* Context instance.
|
||||
*
|
||||
* @var Context Context instance.
|
||||
*/
|
||||
private Context $context;
|
||||
|
||||
/**
|
||||
* Single constructor.
|
||||
*
|
||||
* @param Settings $settings Settings instance.
|
||||
* @param Context $context Context instance.
|
||||
*/
|
||||
public function __construct( Settings $settings, Context $context ) {
|
||||
$this->settings = $settings;
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize admin-related functionality.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register(): void {
|
||||
add_filter( 'admin_body_class', [ $this, 'admin_body_class' ], 99 );
|
||||
add_filter( 'default_content', [ $this, 'prefill_post_content' ], 10, 2 );
|
||||
add_filter( 'default_title', [ $this, 'prefill_post_title' ] );
|
||||
add_filter( 'display_media_states', [ $this, 'media_states' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action to use for registering the service.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @return string Registration action to use.
|
||||
*/
|
||||
public static function get_registration_action(): string {
|
||||
return 'admin_init';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the list of admin classes.
|
||||
*
|
||||
* Makes sure the admin menu is collapsed when accessing
|
||||
* the dashboard and the editor.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string|mixed $class_name Current classes.
|
||||
* @return string|mixed List of Classes.
|
||||
*/
|
||||
public function admin_body_class( $class_name ) {
|
||||
if ( ! $this->context->is_story_editor() ) {
|
||||
return $class_name;
|
||||
}
|
||||
|
||||
// Default WordPress posts list table screen and dashboard.
|
||||
if ( 'post' !== $this->context->get_screen_base() ) {
|
||||
return $class_name;
|
||||
}
|
||||
|
||||
$class_name .= ' edit-story';
|
||||
|
||||
// Overrides regular WordPress behavior by collapsing the admin menu by default.
|
||||
if ( ! str_contains( $class_name, 'folded' ) ) {
|
||||
$class_name .= ' folded';
|
||||
}
|
||||
|
||||
return $class_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-fills post content with a web-story/embed block.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string|mixed $content Default post content.
|
||||
* @param WP_Post|null $post Post object.
|
||||
* @return string|mixed Pre-filled post content if applicable, or the default content otherwise.
|
||||
*/
|
||||
public function prefill_post_content( $content, ?WP_Post $post ) {
|
||||
if ( ! $post ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
if ( ! isset( $_GET['from-web-story'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Story ID.
|
||||
*
|
||||
* @var string $from_web_story
|
||||
*/
|
||||
$from_web_story = $_GET['from-web-story']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
|
||||
$post_id = absint( sanitize_text_field( (string) wp_unslash( $from_web_story ) ) );
|
||||
|
||||
if ( ! $post_id || Story_Post_Type::POST_TYPE_SLUG !== get_post_type( $post_id ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'read_post', $post_id ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$story = new Story();
|
||||
if ( ! $story->load_from_post( $post_id ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
if ( ! $story->get_title() ) {
|
||||
$story->set_title( __( 'Web Story', 'web-stories' ) );
|
||||
}
|
||||
|
||||
$args = [
|
||||
'align' => 'none',
|
||||
'height' => 600,
|
||||
'width' => 360,
|
||||
];
|
||||
|
||||
if ( ! use_block_editor_for_post( $post ) ) {
|
||||
$content = '[web_stories_embed url="%1$s" title="%2$s" poster="%3$s" width="%4$s" height="%5$s" align="%6$s"]';
|
||||
|
||||
return sprintf(
|
||||
$content,
|
||||
esc_url( $story->get_url() ),
|
||||
esc_attr( $story->get_title() ),
|
||||
esc_url( $story->get_poster_portrait() ),
|
||||
absint( $args['width'] ),
|
||||
absint( $args['height'] ),
|
||||
esc_attr( $args['align'] )
|
||||
);
|
||||
}
|
||||
|
||||
$story->set_poster_sizes( '' );
|
||||
$story->set_poster_srcset( '' );
|
||||
$renderer = new Image( $story );
|
||||
$html = $renderer->render( $args );
|
||||
|
||||
$content = '<!-- wp:web-stories/embed {"blockType":"url","url":"%1$s","title":"%2$s","poster":"%3$s","width":"%4$s","height":"%5$s","align":"%6$s","stories": [%7$s]} -->%8$s<!-- /wp:web-stories/embed -->';
|
||||
// note $story->get_url should not be escaped here (esc_url()) see https://github.com/GoogleForCreators/web-stories-wp/issues/11371.
|
||||
return sprintf(
|
||||
$content,
|
||||
$story->get_url(),
|
||||
esc_js( $story->get_title() ),
|
||||
esc_url( $story->get_poster_portrait() ),
|
||||
absint( $args['width'] ),
|
||||
absint( $args['height'] ),
|
||||
esc_js( $args['align'] ),
|
||||
absint( $post_id ),
|
||||
$html
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-fills post title with the story title.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string|mixed $title Default post title.
|
||||
* @return string|mixed Pre-filled post title if applicable, or the default title otherwise.
|
||||
*/
|
||||
public function prefill_post_title( $title ) {
|
||||
if ( ! isset( $_GET['from-web-story'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Story ID.
|
||||
*
|
||||
* @var string $from_web_story
|
||||
*/
|
||||
$from_web_story = $_GET['from-web-story']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
|
||||
$post_id = absint( sanitize_text_field( (string) wp_unslash( $from_web_story ) ) );
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return $title;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'read_post', $post_id ) ) {
|
||||
return $title;
|
||||
}
|
||||
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( ! $post instanceof WP_Post || Story_Post_Type::POST_TYPE_SLUG !== $post->post_type ) {
|
||||
return $title;
|
||||
}
|
||||
|
||||
// Not using get_the_title() because we need the raw title.
|
||||
// Otherwise it runs through wptexturize() and the like, which we want to avoid.
|
||||
return $post->post_title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds active publisher logo to media state output.
|
||||
*
|
||||
* @since 1.23.0
|
||||
*
|
||||
* @param mixed $media_states Array of media states.
|
||||
* @param WP_Post $post Post object.
|
||||
* @return mixed Filtered media states.
|
||||
*/
|
||||
public function media_states( $media_states, WP_Post $post ) {
|
||||
if ( ! \is_array( $media_states ) ) {
|
||||
return $media_states;
|
||||
}
|
||||
|
||||
$active_publisher_logo_id = absint( $this->settings->get_setting( $this->settings::SETTING_NAME_ACTIVE_PUBLISHER_LOGO ) );
|
||||
|
||||
if ( $post->ID === $active_publisher_logo_id ) {
|
||||
$media_states[] = __( 'Web Stories Publisher Logo', 'web-stories' );
|
||||
}
|
||||
return $media_states;
|
||||
}
|
||||
}
|
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Cross_Origin_Isolation.
|
||||
*
|
||||
* Check if editor screen, add cross origin header and add crossorigin attribute to tags.
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Google\Web_Stories\Admin;
|
||||
|
||||
use Google\Web_Stories\Context;
|
||||
use Google\Web_Stories\Infrastructure\HasRequirements;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
use Google\Web_Stories\User\Preferences;
|
||||
|
||||
/**
|
||||
* Class Cross_Origin_Isolation
|
||||
*/
|
||||
class Cross_Origin_Isolation extends Service_Base implements HasRequirements {
|
||||
/**
|
||||
* Context instance.
|
||||
*
|
||||
* @var Context Context instance.
|
||||
*/
|
||||
private Context $context;
|
||||
|
||||
/**
|
||||
* Preferences instance.
|
||||
*
|
||||
* @var Preferences Preferences instance.
|
||||
*/
|
||||
private Preferences $preferences;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @param Preferences $preferences Preferences instance.
|
||||
* @param Context $context Context instance.
|
||||
*/
|
||||
public function __construct( Preferences $preferences, Context $context ) {
|
||||
$this->preferences = $preferences;
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
public function register(): void {
|
||||
if ( ! $this->context->is_story_editor() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'load-post.php', [ $this, 'admin_header' ] );
|
||||
add_action( 'load-post-new.php', [ $this, 'admin_header' ] );
|
||||
add_filter( 'style_loader_tag', [ $this, 'style_loader_tag' ], 10, 3 );
|
||||
add_filter( 'script_loader_tag', [ $this, 'script_loader_tag' ], 10, 3 );
|
||||
add_filter( 'get_avatar', [ $this, 'get_avatar' ], 10, 6 );
|
||||
add_action( 'wp_enqueue_media', [ $this, 'override_media_templates' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action to use for registering the service.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @return string Registration action to use.
|
||||
*/
|
||||
public static function get_registration_action(): string {
|
||||
return 'current_screen';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action priority to use for registering the service.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @return int Registration action priority to use.
|
||||
*/
|
||||
public static function get_registration_action_priority(): int {
|
||||
return 11;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of service IDs required for this service to be registered.
|
||||
*
|
||||
* @since 1.12.0
|
||||
*
|
||||
* @return string[] List of required services.
|
||||
*/
|
||||
public static function get_requirements(): array {
|
||||
return [ 'user_preferences' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Start output buffer to add headers and `crossorigin` attribute everywhere.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public function admin_header(): void {
|
||||
if ( $this->needs_isolation() ) {
|
||||
header( 'Cross-Origin-Opener-Policy: same-origin' );
|
||||
header( 'Cross-Origin-Embedder-Policy: require-corp' );
|
||||
}
|
||||
|
||||
ob_start( [ $this, 'replace_in_dom' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the HTML link tag of an enqueued style.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param mixed $tag The link tag for the enqueued style.
|
||||
* @param string $handle The style's registered handle.
|
||||
* @param string $href The stylesheet's source URL.
|
||||
* @return string|mixed
|
||||
*/
|
||||
public function style_loader_tag( $tag, string $handle, string $href ) {
|
||||
return $this->add_attribute( $tag, 'href', $href );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the HTML script tag of an enqueued script.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param mixed $tag The `<script>` tag for the enqueued script.
|
||||
* @param string $handle The script's registered handle.
|
||||
* @param string $src The script's source URL.
|
||||
* @return string|mixed The filtered script tag.
|
||||
*/
|
||||
public function script_loader_tag( $tag, string $handle, string $src ) {
|
||||
return $this->add_attribute( $tag, 'src', $src );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the avatar tag.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param string|mixed $avatar HTML for the user's avatar.
|
||||
* @param mixed $id_or_email The avatar to retrieve. Accepts a user_id, Gravatar MD5 hash,
|
||||
* user email, WP_User object, WP_Post object, or WP_Comment object.
|
||||
* @param mixed $size Square avatar width and height in pixels to retrieve.
|
||||
* @param mixed $default_url URL for the default image or a default type. Accepts '404', 'retro', 'monsterid',
|
||||
* 'wavatar', 'indenticon', 'mystery', 'mm', 'mysteryman', 'blank', or
|
||||
* 'gravatar_default'. Default is the value of the 'avatar_default' option, with a
|
||||
* fallback of 'mystery'.
|
||||
* @param mixed $alt Alternative text to use in the avatar image tag. Default empty.
|
||||
* @param array<string,mixed> $args Arguments passed to get_avatar_data(), after processing.
|
||||
* @return string|mixed Filtered avatar tag.
|
||||
*/
|
||||
public function get_avatar( $avatar, $id_or_email, $size, $default_url, $alt, array $args ) {
|
||||
return $this->add_attribute( $avatar, 'src', $args['url'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unhook wp_print_media_templates and replace with custom media templates.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public function override_media_templates(): void {
|
||||
remove_action( 'admin_footer', 'wp_print_media_templates' );
|
||||
add_action( 'admin_footer', [ $this, 'custom_print_media_templates' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add crossorigin attribute to all tags that could have assets loaded from a different domain.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public function custom_print_media_templates(): void {
|
||||
ob_start();
|
||||
wp_print_media_templates();
|
||||
$html = (string) ob_get_clean();
|
||||
|
||||
$tags = [
|
||||
'audio',
|
||||
'img',
|
||||
'video',
|
||||
];
|
||||
foreach ( $tags as $tag ) {
|
||||
$html = (string) str_replace( '<' . $tag, '<' . $tag . ' crossorigin="anonymous"', $html );
|
||||
}
|
||||
|
||||
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether "full" cross-origin isolation is needed.
|
||||
*
|
||||
* By default, `crossorigin="anonymous"` attributes are added to all external
|
||||
* resources to make sure they can be accessed programmatically (e.g. by html-to-image).
|
||||
*
|
||||
* However, actual cross-origin isolation by sending COOP and COEP headers is only
|
||||
* needed when video optimization is enabled
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp/issues/9327
|
||||
* @link https://web.dev/coop-coep/
|
||||
*
|
||||
* @return bool Whether the conditional object is needed.
|
||||
*/
|
||||
private function needs_isolation(): bool {
|
||||
$user_id = get_current_user_id();
|
||||
if ( ! $user_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cross-origin isolation is not needed if users can't upload files anyway.
|
||||
if ( ! user_can( $user_id, 'upload_files' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user has opted in to video optimization.
|
||||
*
|
||||
* @var string|bool $preference
|
||||
*/
|
||||
$preference = $this->preferences->get_preference( $user_id, $this->preferences::MEDIA_OPTIMIZATION_META_KEY );
|
||||
|
||||
return rest_sanitize_boolean( $preference );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a html string and add attribute attributes to required tags.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param string $html HTML document as string.
|
||||
* @return string Processed HTML document.
|
||||
*/
|
||||
protected function replace_in_dom( string $html ): string { // phpcs:ignore SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh
|
||||
$site_url = site_url();
|
||||
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin.
|
||||
$tags = [
|
||||
'audio',
|
||||
'img',
|
||||
'link',
|
||||
'script',
|
||||
'video',
|
||||
];
|
||||
|
||||
$tags = implode( '|', $tags );
|
||||
$matches = [];
|
||||
$processed = [];
|
||||
|
||||
if ( preg_match_all( '#<(?P<tag>' . $tags . ')[^<]*?(?:>[\s\S]*?</(?P=tag)>|\s*/>)#', $html, $matches ) ) {
|
||||
|
||||
/**
|
||||
* Single match.
|
||||
*
|
||||
* @var string $match
|
||||
*/
|
||||
foreach ( $matches[0] as $index => $match ) {
|
||||
$tag = $matches['tag'][ $index ];
|
||||
|
||||
if ( str_contains( $match, ' crossorigin=' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$match_value = [];
|
||||
if ( ! preg_match( '/(src|href)=("([^"]+)"|\'([^\']+)\')/', $match, $match_value ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attribute = $match_value[1];
|
||||
$value = $match_value[4] ?? $match_value[3];
|
||||
$cache_key = 'video' === $tag || 'audio' === $tag ? $tag : $attribute;
|
||||
|
||||
// If already processed tag/attribute and value before, skip.
|
||||
if ( isset( $processed[ $cache_key ] ) && \in_array( $value, $processed[ $cache_key ], true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$processed[ $cache_key ][] = $value;
|
||||
|
||||
// The only tags that can have <source> children.
|
||||
if ( 'video' === $tag || 'audio' === $tag ) {
|
||||
if ( ! str_starts_with( $value, $site_url ) && ! str_starts_with( $value, '/' ) ) {
|
||||
$html = str_replace( $match, str_replace( '<' . $tag, '<' . $tag . ' crossorigin="anonymous"', $match ), $html );
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* Modified HTML.
|
||||
*
|
||||
* @var string $html
|
||||
*/
|
||||
$html = $this->add_attribute( $html, $attribute, $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do replacement to add crossorigin attribute.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param string|mixed $html HTML string.
|
||||
* @param string $attribute Attribute to check for.
|
||||
* @param string|null|mixed $url URL.
|
||||
* @return string|mixed Filtered HTML string.
|
||||
*/
|
||||
protected function add_attribute( $html, string $attribute, $url ) {
|
||||
/**
|
||||
* URL.
|
||||
*
|
||||
* @var string $url
|
||||
*/
|
||||
if ( ! $url || ! \is_string( $html ) ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
$site_url = site_url();
|
||||
$url = esc_url( $url );
|
||||
|
||||
if ( str_starts_with( $url, $site_url ) ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
if ( str_starts_with( $url, '/' ) ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
[
|
||||
$attribute . '="' . $url . '"',
|
||||
"{$attribute}='{$url}'",
|
||||
],
|
||||
[
|
||||
'crossorigin="anonymous" ' . $attribute . '="' . $url . '"',
|
||||
"crossorigin='anonymous' {$attribute}='{$url}'",
|
||||
],
|
||||
$html
|
||||
);
|
||||
}
|
||||
}
|
747
wp-content/plugins/web-stories/includes/Admin/Customizer.php
Normal file
747
wp-content/plugins/web-stories/includes/Admin/Customizer.php
Normal file
@@ -0,0 +1,747 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Customizer
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2020 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Google\Web_Stories\Admin;
|
||||
|
||||
use Google\Web_Stories\Infrastructure\Conditional;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
use Google\Web_Stories\Settings;
|
||||
use Google\Web_Stories\Stories_Script_Data;
|
||||
use Google\Web_Stories\Story_Post_Type;
|
||||
use Google\Web_Stories\Story_Query;
|
||||
use WP_Customize_Manager;
|
||||
use WP_Customize_Setting;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class customizer settings.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
|
||||
*
|
||||
* @phpstan-type ThemeSupport array{
|
||||
* customizer: array{
|
||||
* view_type: array{default: string, enabled: string[]},
|
||||
* title: array{default: bool, enabled: bool},
|
||||
* excerpt: array{default: bool, enabled: bool},
|
||||
* author: array{default: bool, enabled: bool},
|
||||
* date: array{default: bool, enabled: bool},
|
||||
* show_archive_link: array{default: bool, enabled: bool},
|
||||
* archive_link: array{default: bool|string, enabled: bool|string, label: string},
|
||||
* sharp_corners: array{default: bool, enabled: bool},
|
||||
* order: array{default: string},
|
||||
* orderby: array{default: string},
|
||||
* circle_size: array{default: int},
|
||||
* number_of_stories: array{default: int},
|
||||
* number_of_columns: array{default: int},
|
||||
* image_alignment: array{default: string}
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @phpstan-type StoryAttributes array{
|
||||
* view_type?: string,
|
||||
* number_of_columns?: int,
|
||||
* show_title?: bool,
|
||||
* show_author?: bool,
|
||||
* show_date?: bool,
|
||||
* show_archive_link?: bool|string,
|
||||
* show_excerpt?: bool,
|
||||
* image_alignment?: string,
|
||||
* class?: string,
|
||||
* show_stories?: bool|string,
|
||||
* archive_link_label?: string,
|
||||
* circle_size?: int,
|
||||
* sharp_corners?: bool,
|
||||
* order?: string,
|
||||
* orderby?: string,
|
||||
* number_of_stories?: int
|
||||
* }
|
||||
*/
|
||||
class Customizer extends Service_Base implements Conditional {
|
||||
|
||||
/**
|
||||
* Customizer section slug.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public const SECTION_SLUG = 'web_story_options';
|
||||
|
||||
/**
|
||||
* Customizer web stories options key.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public const STORY_OPTION = 'web_stories_customizer_settings';
|
||||
|
||||
/**
|
||||
* WP_Customize_Manager instance.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @var WP_Customize_Manager $wp_customize WP_Customize_Manager instance.
|
||||
*/
|
||||
private WP_Customize_Manager $wp_customize;
|
||||
|
||||
/**
|
||||
* Settings instance.
|
||||
*
|
||||
* @var Settings Settings instance.
|
||||
*/
|
||||
private Settings $settings;
|
||||
|
||||
/**
|
||||
* Story_Post_Type instance.
|
||||
*
|
||||
* @var Story_Post_Type Story_Post_Type instance.
|
||||
*/
|
||||
private Story_Post_Type $story_post_type;
|
||||
|
||||
/**
|
||||
* Stories_Script_Data instance.
|
||||
*
|
||||
* @var Stories_Script_Data Stories_Script_Data instance.
|
||||
*/
|
||||
protected Stories_Script_Data $stories_script_data;
|
||||
|
||||
/**
|
||||
* Analytics constructor.
|
||||
*
|
||||
* @since 1.12.0
|
||||
*
|
||||
* @param Settings $settings Settings instance.
|
||||
* @param Story_Post_Type $story_post_type Story_Post_Type instance.
|
||||
* @param Stories_Script_Data $stories_script_data Stories_Script_Data instance.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
Settings $settings,
|
||||
Story_Post_Type $story_post_type,
|
||||
Stories_Script_Data $stories_script_data
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
$this->story_post_type = $story_post_type;
|
||||
$this->stories_script_data = $stories_script_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the customizer logic.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public function register(): void {
|
||||
add_action( 'customize_register', [ $this, 'register_customizer_settings' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the conditional object is currently needed.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @return bool Whether the conditional object is needed.
|
||||
*/
|
||||
public static function is_needed(): bool {
|
||||
return ! \function_exists( 'wp_is_block_theme' ) || ! wp_is_block_theme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers web stories customizer settings.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
* @SuppressWarnings(PHPMD.NPathComplexity)
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @param WP_Customize_Manager $wp_customize WP_Customize_Manager instance.
|
||||
*/
|
||||
public function register_customizer_settings( WP_Customize_Manager $wp_customize ): void {
|
||||
$this->wp_customize = $wp_customize;
|
||||
|
||||
$theme_support = $this->get_stories_theme_support()['customizer'];
|
||||
|
||||
$active_callback = fn() => $this->is_option_enabled( 'show_stories' );
|
||||
|
||||
$wp_customize->add_section(
|
||||
self::SECTION_SLUG,
|
||||
[
|
||||
'title' => esc_html__( 'Web Stories', 'web-stories' ),
|
||||
'theme_supports' => 'web-stories',
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[show_stories]',
|
||||
[
|
||||
'default' => false,
|
||||
'type' => 'option',
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[show_stories]',
|
||||
[
|
||||
'type' => 'checkbox',
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Display stories', 'web-stories' ),
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[view_type]',
|
||||
[
|
||||
'default' => $theme_support['view_type']['default'],
|
||||
'type' => 'option',
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[view_type]',
|
||||
[
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => _x( 'View Type', 'noun', 'web-stories' ),
|
||||
'type' => 'select',
|
||||
'choices' => $this->get_view_type_choices( $theme_support['view_type']['enabled'] ),
|
||||
'active_callback' => $active_callback,
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[number_of_stories]',
|
||||
[
|
||||
'default' => $theme_support['number_of_stories']['default'],
|
||||
'type' => 'option',
|
||||
'validate_callback' => [ $this, 'validate_number_of_stories' ],
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[number_of_stories]',
|
||||
[
|
||||
'type' => 'number',
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Number of Stories', 'web-stories' ),
|
||||
'input_attrs' => [
|
||||
'min' => 1,
|
||||
'max' => 20,
|
||||
],
|
||||
'active_callback' => $active_callback,
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[number_of_columns]',
|
||||
[
|
||||
'default' => $theme_support['number_of_columns']['default'],
|
||||
'type' => 'option',
|
||||
'validate_callback' => [ $this, 'validate_number_of_columns' ],
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[number_of_columns]',
|
||||
[
|
||||
'type' => 'number',
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Number of Columns', 'web-stories' ),
|
||||
'input_attrs' => [
|
||||
'min' => 1,
|
||||
'max' => 4,
|
||||
],
|
||||
'active_callback' => fn() => ( $this->is_option_enabled( 'show_stories' ) && $this->is_view_type( 'grid' ) ),
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[orderby]',
|
||||
[
|
||||
'default' => $theme_support['orderby']['default'],
|
||||
'type' => 'option',
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[orderby]',
|
||||
[
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Order By', 'web-stories' ),
|
||||
'type' => 'select',
|
||||
'choices' => [
|
||||
'post_title' => __( 'Title', 'web-stories' ),
|
||||
'post_date' => __( 'Date', 'web-stories' ),
|
||||
],
|
||||
'active_callback' => $active_callback,
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[order]',
|
||||
[
|
||||
'default' => $theme_support['order']['default'],
|
||||
'type' => 'option',
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[order]',
|
||||
[
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Order', 'web-stories' ),
|
||||
'type' => 'select',
|
||||
'choices' => [
|
||||
'ASC' => __( 'Ascending', 'web-stories' ),
|
||||
'DESC' => __( 'Descending', 'web-stories' ),
|
||||
],
|
||||
'active_callback' => $active_callback,
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[circle_size]',
|
||||
[
|
||||
'default' => $theme_support['circle_size']['default'],
|
||||
'type' => 'option',
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[circle_size]',
|
||||
[
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Circle Size', 'web-stories' ),
|
||||
'type' => 'number',
|
||||
'input_attrs' => [
|
||||
'min' => 80,
|
||||
'max' => 200,
|
||||
'step' => 5,
|
||||
],
|
||||
'active_callback' => fn() => $this->is_option_enabled( 'show_stories' ) && $this->is_view_type( 'circles' ),
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[image_alignment]',
|
||||
[
|
||||
'type' => 'option',
|
||||
'default' => $theme_support['image_alignment']['default'],
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[image_alignment]',
|
||||
[
|
||||
'type' => 'radio',
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Image Alignment', 'web-stories' ),
|
||||
'choices' => [
|
||||
'left' => __( 'Left', 'web-stories' ),
|
||||
'right' => __( 'Right', 'web-stories' ),
|
||||
],
|
||||
'active_callback' => fn() => ( $this->is_option_enabled( 'show_stories' ) && $this->is_view_type( 'list' ) ),
|
||||
]
|
||||
);
|
||||
|
||||
if ( $theme_support['title']['enabled'] ) {
|
||||
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[show_title]',
|
||||
[
|
||||
'default' => $theme_support['title']['default'],
|
||||
'type' => 'option',
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[show_title]',
|
||||
[
|
||||
'type' => 'checkbox',
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Display Title', 'web-stories' ),
|
||||
'active_callback' => $active_callback,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( $theme_support['excerpt']['enabled'] ) {
|
||||
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[show_excerpt]',
|
||||
[
|
||||
'default' => $theme_support['excerpt']['default'],
|
||||
'type' => 'option',
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[show_excerpt]',
|
||||
[
|
||||
'type' => 'checkbox',
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Display Excerpt', 'web-stories' ),
|
||||
'active_callback' => fn() => $this->is_option_enabled( 'show_stories' ) && $this->is_view_type( 'list' ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( $theme_support['author']['enabled'] ) {
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[show_author]',
|
||||
[
|
||||
'default' => $theme_support['author']['default'],
|
||||
'type' => 'option',
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[show_author]',
|
||||
[
|
||||
'type' => 'checkbox',
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Display Author', 'web-stories' ),
|
||||
'active_callback' => fn() => ( $this->is_option_enabled( 'show_stories' ) && ! $this->is_view_type( 'circles' ) ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( $theme_support['date']['enabled'] ) {
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[show_date]',
|
||||
[
|
||||
'default' => $theme_support['date']['default'],
|
||||
'type' => 'option',
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[show_date]',
|
||||
[
|
||||
'type' => 'checkbox',
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Display Date', 'web-stories' ),
|
||||
'active_callback' => fn() => ( $this->is_option_enabled( 'show_stories' ) && ! $this->is_view_type( 'circles' ) ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( $theme_support['sharp_corners']['enabled'] ) {
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[sharp_corners]',
|
||||
[
|
||||
'default' => $theme_support['sharp_corners']['default'],
|
||||
'type' => 'option',
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[sharp_corners]',
|
||||
[
|
||||
'type' => 'checkbox',
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Sharp Corners', 'web-stories' ),
|
||||
'active_callback' => fn() => ( $this->is_option_enabled( 'show_stories' ) && ! $this->is_view_type( 'circles' ) ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( $theme_support['archive_link']['enabled'] ) {
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[show_archive_link]',
|
||||
[
|
||||
'default' => $theme_support['archive_link']['default'],
|
||||
'type' => 'option',
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[show_archive_link]',
|
||||
[
|
||||
'type' => 'checkbox',
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Display Archive Link', 'web-stories' ),
|
||||
'active_callback' => $active_callback,
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_setting(
|
||||
self::STORY_OPTION . '[archive_link_label]',
|
||||
[
|
||||
'type' => 'option',
|
||||
'default' => $theme_support['archive_link']['label'],
|
||||
]
|
||||
);
|
||||
|
||||
$wp_customize->add_control(
|
||||
self::STORY_OPTION . '[archive_link_label]',
|
||||
[
|
||||
'type' => 'text',
|
||||
'section' => self::SECTION_SLUG,
|
||||
'label' => __( 'Archive Link Label', 'web-stories' ),
|
||||
'active_callback' => fn() => ( $this->is_option_enabled( 'show_stories' ) && $this->is_option_enabled( 'show_archive_link' ) ),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the number of story setting value.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @param WP_Error $validity WP_Error object.
|
||||
* @param int $value Value to be validated.
|
||||
*/
|
||||
public function validate_number_of_stories( WP_Error $validity, int $value ): WP_Error {
|
||||
$value = (int) $value;
|
||||
|
||||
if ( $value <= 0 || $value > 20 ) {
|
||||
$validity->add( 'invalid_number', __( 'The number of stories must be between 1 and 20.', 'web-stories' ) );
|
||||
}
|
||||
return $validity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the number of columns setting value.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @param WP_Error $validity WP_Error object.
|
||||
* @param int $value Value to be validated.
|
||||
*/
|
||||
public function validate_number_of_columns( WP_Error $validity, int $value ): WP_Error {
|
||||
$value = (int) $value;
|
||||
|
||||
if ( $value <= 0 || $value > 5 ) {
|
||||
$validity->add( 'invalid_number', __( 'The number of columns must be between 1 and 4.', 'web-stories' ) );
|
||||
}
|
||||
return $validity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders web stories based on the customizer selected options.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.NPathComplexity)
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public function render_stories(): string {
|
||||
/**
|
||||
* Render options.
|
||||
*
|
||||
* @var array<string,string|bool> $options
|
||||
* @phpstan-var StoryAttributes
|
||||
*/
|
||||
$options = (array) $this->settings->get_setting( self::STORY_OPTION );
|
||||
|
||||
if ( empty( $options['show_stories'] ) || true !== $options['show_stories'] ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$theme_support = $this->get_stories_theme_support()['customizer'];
|
||||
|
||||
$story_attributes = [
|
||||
'view_type' => $options['view_type'] ?? $theme_support['view_type']['default'],
|
||||
'show_title' => isset( $options['show_title'] ) ? (bool) $options['show_title'] : $theme_support['title']['default'],
|
||||
'show_excerpt' => isset( $options['show_excerpt'] ) ? (bool) $options['show_excerpt'] : $theme_support['excerpt']['default'],
|
||||
'show_author' => isset( $options['show_author'] ) ? (bool) $options['show_author'] : $theme_support['author']['default'],
|
||||
'show_date' => isset( $options['show_date'] ) ? (bool) $options['show_date'] : $theme_support['date']['default'],
|
||||
'show_archive_link' => isset( $options['show_archive_link'] ) ? (bool) $options['show_archive_link'] : $theme_support['archive_link']['default'],
|
||||
'archive_link_label' => isset( $options['archive_link_label'] ) ? (string) $options['archive_link_label'] : $theme_support['archive_link']['label'],
|
||||
'circle_size' => isset( $options['circle_size'] ) ? (int) $options['circle_size'] : $theme_support['circle_size']['default'],
|
||||
'sharp_corners' => isset( $options['sharp_corners'] ) ? (bool) $options['sharp_corners'] : $theme_support['sharp_corners']['default'],
|
||||
'image_alignment' => isset( $options['image_alignment'] ) ? (string) $options['image_alignment'] : $theme_support['image_alignment']['default'],
|
||||
'number_of_columns' => isset( $options['number_of_columns'] ) ? (int) $options['number_of_columns'] : $theme_support['number_of_columns']['default'],
|
||||
'class' => 'web-stories-list--customizer',
|
||||
];
|
||||
|
||||
$query_arguments = [
|
||||
'posts_per_page' => isset( $options['number_of_stories'] ) ? (int) $options['number_of_stories'] : $theme_support['number_of_stories']['default'], // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
|
||||
'orderby' => isset( $options['orderby'] ) ? (string) $options['orderby'] : $theme_support['orderby']['default'],
|
||||
'order' => isset( $options['order'] ) ? (string) $options['order'] : $theme_support['order']['default'],
|
||||
];
|
||||
|
||||
return ( new Story_Query( $story_attributes, $query_arguments ) )->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get theme support configuration.
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @return array Theme support configuration
|
||||
*
|
||||
* @phpstan-return ThemeSupport
|
||||
*/
|
||||
public function get_stories_theme_support(): array {
|
||||
/**
|
||||
* Theme support configuration.
|
||||
*
|
||||
* @var ThemeSupport[]|array<int, false> $support
|
||||
*/
|
||||
$support = get_theme_support( 'web-stories' );
|
||||
$support = isset( $support[0] ) && \is_array( $support[0] ) ? $support[0] : [];
|
||||
|
||||
$has_archive = (bool) $this->story_post_type->get_has_archive();
|
||||
|
||||
$default_support = [
|
||||
'customizer' => [
|
||||
'view_type' => [
|
||||
'enabled' => [ 'circles' ],
|
||||
'default' => 'circles',
|
||||
],
|
||||
'title' => [
|
||||
'enabled' => true,
|
||||
'default' => true,
|
||||
],
|
||||
'excerpt' => [
|
||||
'enabled' => true,
|
||||
'default' => false,
|
||||
],
|
||||
'author' => [
|
||||
'enabled' => true,
|
||||
'default' => true,
|
||||
],
|
||||
'date' => [
|
||||
'enabled' => false,
|
||||
'default' => false,
|
||||
],
|
||||
'archive_link' => [
|
||||
'enabled' => $has_archive,
|
||||
'default' => $has_archive,
|
||||
'label' => __( 'View all stories', 'web-stories' ),
|
||||
],
|
||||
'sharp_corners' => [
|
||||
'enabled' => false,
|
||||
'default' => false,
|
||||
],
|
||||
'order' => [
|
||||
'default' => 'DESC',
|
||||
],
|
||||
'orderby' => [
|
||||
'default' => 'post_date',
|
||||
],
|
||||
'circle_size' => [
|
||||
'default' => 150,
|
||||
],
|
||||
'number_of_stories' => [
|
||||
'default' => 10,
|
||||
],
|
||||
'number_of_columns' => [
|
||||
'default' => 2,
|
||||
],
|
||||
'image_alignment' => [
|
||||
'default' => is_rtl() ? 'right' : 'left',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Theme support config.
|
||||
*
|
||||
* @var ThemeSupport $support
|
||||
*/
|
||||
$support = $this->parse_args( $support, $default_support );
|
||||
|
||||
return $support;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the view type choices.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @param array<string,mixed> $view_type View type to check.
|
||||
* @return array<string,mixed> An array of view type choices.
|
||||
*/
|
||||
private function get_view_type_choices( array $view_type ): array {
|
||||
$view_type_choices = $this->stories_script_data->get_layouts();
|
||||
|
||||
if ( empty( $view_type ) ) {
|
||||
return $view_type_choices;
|
||||
}
|
||||
|
||||
return array_intersect_key( $view_type_choices, array_fill_keys( $view_type, true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given option is enabled or not.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @param string $option_name The name of the option to check.
|
||||
* @return bool Returns true if the given option is enabled otherwise false.
|
||||
*/
|
||||
private function is_option_enabled( string $option_name ): bool {
|
||||
$setting = $this->wp_customize->get_setting( self::STORY_OPTION . "[{$option_name}]" );
|
||||
return ( $setting instanceof WP_Customize_Setting && true === $setting->value() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the current view type.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @param string $view_type View type to check.
|
||||
* @return bool Whether or not current view type matches the one passed.
|
||||
*/
|
||||
private function is_view_type( string $view_type ): bool {
|
||||
$setting = $this->wp_customize->get_setting( self::STORY_OPTION . '[view_type]' );
|
||||
return ( $setting instanceof WP_Customize_Setting && $view_type === $setting->value() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges user defined arguments into defaults array.
|
||||
*
|
||||
* Like wp_parse_args(), but recursive.
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @see wp_parse_args()
|
||||
*
|
||||
* @param array<string, mixed> $args Value to merge with $defaults.
|
||||
* @param array<string, array<string, array<string, array<int,string>|bool|int|string>>> $defaults Optional. Array that serves as the defaults. Default empty array.
|
||||
* @return array<string, mixed> Merged user defined values with defaults.
|
||||
*/
|
||||
private function parse_args( array $args, array $defaults = [] ): array {
|
||||
$parsed_args = $defaults;
|
||||
|
||||
foreach ( $args as $key => $value ) {
|
||||
if ( \is_array( $value ) && isset( $parsed_args[ $key ] ) ) {
|
||||
/**
|
||||
* Default value.
|
||||
*
|
||||
* @var array<string, array<string, array<string, array<int,string>|bool|int|string>>> $def
|
||||
*/
|
||||
$def = $parsed_args[ $key ];
|
||||
$parsed_args[ $key ] = $this->parse_args( $value, $def );
|
||||
} else {
|
||||
$parsed_args[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $parsed_args;
|
||||
}
|
||||
}
|
576
wp-content/plugins/web-stories/includes/Admin/Dashboard.php
Normal file
576
wp-content/plugins/web-stories/includes/Admin/Dashboard.php
Normal file
@@ -0,0 +1,576 @@
|
||||
<?php
|
||||
/**
|
||||
* Dashboard class.
|
||||
*
|
||||
* Responsible for adding the stories dashboard to WordPress admin.
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2020 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Google\Web_Stories\Admin;
|
||||
|
||||
use Google\Web_Stories\Assets;
|
||||
use Google\Web_Stories\Context;
|
||||
use Google\Web_Stories\Decoder;
|
||||
use Google\Web_Stories\Experiments;
|
||||
use Google\Web_Stories\Font_Post_Type;
|
||||
use Google\Web_Stories\Integrations\Site_Kit;
|
||||
use Google\Web_Stories\Integrations\WooCommerce;
|
||||
use Google\Web_Stories\Locale;
|
||||
use Google\Web_Stories\Media\Types;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
use Google\Web_Stories\Settings;
|
||||
use Google\Web_Stories\Shopping\Shopping_Vendors;
|
||||
use Google\Web_Stories\Story_Post_Type;
|
||||
use Google\Web_Stories\Tracking;
|
||||
|
||||
/**
|
||||
* Dashboard class.
|
||||
*/
|
||||
class Dashboard extends Service_Base {
|
||||
|
||||
/**
|
||||
* Script handle.
|
||||
*/
|
||||
public const SCRIPT_HANDLE = 'web-stories-dashboard';
|
||||
|
||||
/**
|
||||
* Admin page hook suffixes.
|
||||
*
|
||||
* @var array<string,string|bool> List of the admin pages' hook_suffix values.
|
||||
*/
|
||||
private array $hook_suffix = [];
|
||||
|
||||
/**
|
||||
* Experiments instance.
|
||||
*
|
||||
* @var Experiments Experiments instance.
|
||||
*/
|
||||
private Experiments $experiments;
|
||||
|
||||
/**
|
||||
* Site_Kit instance.
|
||||
*
|
||||
* @var Site_Kit Site_Kit instance.
|
||||
*/
|
||||
private Site_Kit $site_kit;
|
||||
|
||||
/**
|
||||
* Decoder instance.
|
||||
*
|
||||
* @var Decoder Decoder instance.
|
||||
*/
|
||||
private Decoder $decoder;
|
||||
|
||||
/**
|
||||
* Locale instance.
|
||||
*
|
||||
* @var Locale Locale instance.
|
||||
*/
|
||||
private Locale $locale;
|
||||
|
||||
/**
|
||||
* Google_Fonts instance.
|
||||
*
|
||||
* @var Google_Fonts Google_Fonts instance.
|
||||
*/
|
||||
private Google_Fonts $google_fonts;
|
||||
|
||||
/**
|
||||
* Assets instance.
|
||||
*
|
||||
* @var Assets Assets instance.
|
||||
*/
|
||||
private Assets $assets;
|
||||
|
||||
/**
|
||||
* Story_Post_Type instance.
|
||||
*
|
||||
* @var Story_Post_Type Story_Post_Type instance.
|
||||
*/
|
||||
private Story_Post_Type $story_post_type;
|
||||
|
||||
/**
|
||||
* Font_Post_Type instance.
|
||||
*
|
||||
* @var Font_Post_Type Font_Post_Type instance.
|
||||
*/
|
||||
private Font_Post_Type $font_post_type;
|
||||
|
||||
/**
|
||||
* Context instance.
|
||||
*
|
||||
* @var Context Context instance.
|
||||
*/
|
||||
private Context $context;
|
||||
|
||||
/**
|
||||
* Types instance.
|
||||
*
|
||||
* @var Types Types instance.
|
||||
*/
|
||||
private Types $types;
|
||||
|
||||
/**
|
||||
* Shopping_Vendors instance.
|
||||
*
|
||||
* @var Shopping_Vendors Shopping_Vendors instance.
|
||||
*/
|
||||
private Shopping_Vendors $shopping_vendors;
|
||||
|
||||
/**
|
||||
* WooCommerce instance.
|
||||
*
|
||||
* @var WooCommerce WooCommerce instance.
|
||||
*/
|
||||
private WooCommerce $woocommerce;
|
||||
|
||||
/**
|
||||
* Settings instance.
|
||||
*
|
||||
* @var Settings Settings instance.
|
||||
*/
|
||||
private Settings $settings;
|
||||
|
||||
/**
|
||||
* Dashboard constructor.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param Experiments $experiments Experiments instance.
|
||||
* @param Site_Kit $site_kit Site_Kit instance.
|
||||
* @param Decoder $decoder Decoder instance.
|
||||
* @param Locale $locale Locale instance.
|
||||
* @param Google_Fonts $google_fonts Google_Fonts instance.
|
||||
* @param Assets $assets Assets instance.
|
||||
* @param Font_Post_Type $font_post_type Font_Post_Type instance.
|
||||
* @param Story_Post_Type $story_post_type Story_Post_Type instance.
|
||||
* @param Context $context Context instance.
|
||||
* @param Types $types Types instance.
|
||||
* @param Shopping_Vendors $shopping_vendors Shopping_Vendors instance.
|
||||
* @param WooCommerce $woocommerce WooCommerce instance.
|
||||
* @param Settings $settings Settings instance.
|
||||
*/
|
||||
public function __construct(
|
||||
Experiments $experiments,
|
||||
Site_Kit $site_kit,
|
||||
Decoder $decoder,
|
||||
Locale $locale,
|
||||
Google_Fonts $google_fonts,
|
||||
Assets $assets,
|
||||
Font_Post_Type $font_post_type,
|
||||
Story_Post_Type $story_post_type,
|
||||
Context $context,
|
||||
Types $types,
|
||||
Shopping_Vendors $shopping_vendors,
|
||||
WooCommerce $woocommerce,
|
||||
Settings $settings
|
||||
) {
|
||||
$this->experiments = $experiments;
|
||||
$this->decoder = $decoder;
|
||||
$this->site_kit = $site_kit;
|
||||
$this->locale = $locale;
|
||||
$this->google_fonts = $google_fonts;
|
||||
$this->assets = $assets;
|
||||
$this->font_post_type = $font_post_type;
|
||||
$this->story_post_type = $story_post_type;
|
||||
$this->context = $context;
|
||||
$this->types = $types;
|
||||
$this->shopping_vendors = $shopping_vendors;
|
||||
$this->woocommerce = $woocommerce;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the dashboard logic.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register(): void {
|
||||
add_action( 'admin_menu', [ $this, 'add_menu_page' ] );
|
||||
add_action( 'admin_init', [ $this, 'redirect_menu_page' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
add_action( 'admin_notices', [ $this, 'display_link_to_dashboard' ] );
|
||||
add_action( 'load-web-story_page_stories-dashboard', [ $this, 'load_stories_dashboard' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the admin page's hook suffix.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $key The current admin page key.
|
||||
* @return bool|string The dashboard page's hook_suffix, or false if the user does not have the capability required.
|
||||
*/
|
||||
public function get_hook_suffix( string $key ) {
|
||||
return $this->hook_suffix[ $key ] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the dashboard admin menu page.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_menu_page(): void {
|
||||
$parent = 'edit.php?post_type=' . $this->story_post_type->get_slug();
|
||||
|
||||
$settings = $this->get_dashboard_settings();
|
||||
|
||||
/**
|
||||
* The edit_posts capability.
|
||||
*
|
||||
* @var string $edit_posts
|
||||
*/
|
||||
$edit_posts = $this->story_post_type->get_cap_name( 'edit_posts' );
|
||||
|
||||
$this->hook_suffix['stories-dashboard'] = add_submenu_page(
|
||||
$parent,
|
||||
__( 'Dashboard', 'web-stories' ),
|
||||
__( 'Dashboard', 'web-stories' ),
|
||||
$edit_posts, // phpcs:ignore WordPress.WP.Capabilities.Undetermined
|
||||
'stories-dashboard',
|
||||
[ $this, 'render' ],
|
||||
0
|
||||
);
|
||||
|
||||
if ( isset( $settings['canViewDefaultTemplates'] ) && $settings['canViewDefaultTemplates'] ) {
|
||||
$this->hook_suffix['stories-dashboard-explore'] = add_submenu_page(
|
||||
$parent,
|
||||
__( 'Explore Templates', 'web-stories' ),
|
||||
__( 'Explore Templates', 'web-stories' ),
|
||||
$edit_posts, // phpcs:ignore WordPress.WP.Capabilities.Undetermined
|
||||
'stories-dashboard#/templates-gallery',
|
||||
'__return_null',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
$this->hook_suffix['stories-dashboard-settings'] = add_submenu_page(
|
||||
$parent,
|
||||
__( 'Settings', 'web-stories' ),
|
||||
__( 'Settings', 'web-stories' ),
|
||||
$edit_posts, // phpcs:ignore WordPress.WP.Capabilities.Undetermined
|
||||
'stories-dashboard#/editor-settings',
|
||||
'__return_null',
|
||||
20
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to the correct Dashboard page when clicking on the top-level "Stories" menu item.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function redirect_menu_page(): void {
|
||||
global $pagenow;
|
||||
|
||||
if ( ! isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page slug.
|
||||
*
|
||||
* @var string $page
|
||||
*/
|
||||
$page = $_GET['page']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$page = sanitize_text_field( (string) wp_unslash( $page ) );
|
||||
|
||||
if ( 'admin.php' === $pagenow && 'stories-dashboard' === $page ) {
|
||||
wp_safe_redirect(
|
||||
add_query_arg(
|
||||
[
|
||||
'post_type' => $this->story_post_type->get_slug(),
|
||||
'page' => 'stories-dashboard',
|
||||
],
|
||||
admin_url( 'edit.php' )
|
||||
)
|
||||
);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload API requests in the dashboard.
|
||||
*
|
||||
* Important: keep in sync with usage & definition in React app.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function load_stories_dashboard(): void {
|
||||
$rest_url = trailingslashit( $this->story_post_type->get_rest_url() );
|
||||
|
||||
$preload_paths = [
|
||||
'/web-stories/v1/settings/',
|
||||
'/web-stories/v1/publisher-logos/',
|
||||
'/web-stories/v1/users/me/',
|
||||
'/web-stories/v1/taxonomies/?' . build_query(
|
||||
[
|
||||
'type' => $this->story_post_type->get_slug(),
|
||||
'context' => 'edit',
|
||||
'hierarchical' => 'true',
|
||||
'show_ui' => 'true',
|
||||
]
|
||||
),
|
||||
$rest_url . '?' . build_query(
|
||||
[
|
||||
'_embed' => rawurlencode(
|
||||
implode(
|
||||
',',
|
||||
[ 'wp:lock', 'author' ]
|
||||
)
|
||||
),
|
||||
'context' => 'edit',
|
||||
'order' => 'desc',
|
||||
'orderby' => 'modified',
|
||||
'page' => 1,
|
||||
'per_page' => 24,
|
||||
'status' => rawurlencode(
|
||||
implode(
|
||||
',',
|
||||
[ 'draft', 'future', 'pending', 'publish', 'private' ]
|
||||
)
|
||||
),
|
||||
'_web_stories_envelope' => 'true',
|
||||
'_fields' => rawurlencode(
|
||||
implode(
|
||||
',',
|
||||
[
|
||||
'id',
|
||||
'title',
|
||||
'status',
|
||||
'date',
|
||||
'date_gmt',
|
||||
'modified',
|
||||
'modified_gmt',
|
||||
'story_poster',
|
||||
'link',
|
||||
'preview_link',
|
||||
'edit_link',
|
||||
'_links', // Needed for WP 6.1+.
|
||||
'_embedded',
|
||||
// _web_stories_envelope will add these fields, we need them too.
|
||||
'body',
|
||||
'status',
|
||||
'headers',
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
];
|
||||
|
||||
/**
|
||||
* Preload common data by specifying an array of REST API paths that will be preloaded.
|
||||
*
|
||||
* Filters the array of paths that will be preloaded.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string[] $preload_paths Array of paths to preload.
|
||||
*/
|
||||
$preload_paths = apply_filters( 'web_stories_dashboard_preload_paths', $preload_paths );
|
||||
|
||||
$preload_data = array_reduce(
|
||||
$preload_paths,
|
||||
'\Google\Web_Stories\rest_preload_api_request',
|
||||
[]
|
||||
);
|
||||
|
||||
wp_add_inline_script(
|
||||
'wp-api-fetch',
|
||||
sprintf( 'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );', wp_json_encode( $preload_data ) ),
|
||||
'after'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the dashboard page.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function render(): void {
|
||||
require_once WEBSTORIES_PLUGIN_DIR_PATH . 'includes/templates/admin/dashboard.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues dashboard scripts and styles.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $hook_suffix The current admin page.
|
||||
*/
|
||||
public function enqueue_assets( string $hook_suffix ): void {
|
||||
if ( $this->get_hook_suffix( 'stories-dashboard' ) !== $hook_suffix ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->assets->enqueue_script_asset( self::SCRIPT_HANDLE, [ Tracking::SCRIPT_HANDLE ], false );
|
||||
|
||||
$this->assets->enqueue_style_asset( self::SCRIPT_HANDLE, [ $this->google_fonts::SCRIPT_HANDLE ] );
|
||||
|
||||
wp_localize_script(
|
||||
self::SCRIPT_HANDLE,
|
||||
'webStories',
|
||||
[
|
||||
'publicPath' => $this->assets->get_base_url( 'assets/js/' ), // Required before the editor script is enqueued.
|
||||
'localeData' => $this->assets->get_translations( self::SCRIPT_HANDLE ), // Required for i18n setLocaleData.
|
||||
]
|
||||
);
|
||||
|
||||
// Dequeue forms.css, see https://github.com/googleforcreators/web-stories-wp/issues/349 .
|
||||
$this->assets->remove_admin_style( [ 'forms' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dashboard settings as an array.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array<string,bool|string|int|array<string,mixed>>
|
||||
*/
|
||||
public function get_dashboard_settings(): array {
|
||||
$new_story_url = admin_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'post_type' => $this->story_post_type->get_slug(),
|
||||
],
|
||||
'post-new.php'
|
||||
)
|
||||
);
|
||||
|
||||
// Media settings.
|
||||
$max_upload_size = wp_max_upload_size();
|
||||
if ( ! $max_upload_size ) {
|
||||
$max_upload_size = 0;
|
||||
}
|
||||
$mime_types = $this->types->get_allowed_mime_types();
|
||||
$allowed_image_mime_types = $mime_types['image'];
|
||||
$vendors = wp_list_pluck( $this->shopping_vendors->get_vendors(), 'label' );
|
||||
|
||||
$auto_advance = $this->settings->get_setting( $this->settings::SETTING_NAME_AUTO_ADVANCE );
|
||||
$page_duration = $this->settings->get_setting( $this->settings::SETTING_NAME_DEFAULT_PAGE_DURATION );
|
||||
|
||||
$plugin_file = plugin_basename( WEBSTORIES_PLUGIN_FILE );
|
||||
$auto_updates = (array) get_site_option( 'auto_update_plugins', [] );
|
||||
$auto_updates_enabled = \in_array( $plugin_file, $auto_updates, true );
|
||||
$plugin_updates = get_site_transient( 'update_plugins' );
|
||||
$needs_update = \is_object( $plugin_updates ) &&
|
||||
property_exists( $plugin_updates, 'response' ) &&
|
||||
\is_array( $plugin_updates->response ) &&
|
||||
! empty( $plugin_updates->response[ $plugin_file ] );
|
||||
$can_update = current_user_can( 'update_plugins' );
|
||||
|
||||
$settings = [
|
||||
'isRTL' => is_rtl(),
|
||||
'userId' => get_current_user_id(),
|
||||
'locale' => $this->locale->get_locale_settings(),
|
||||
'newStoryURL' => $new_story_url,
|
||||
'archiveURL' => $this->story_post_type->get_archive_link(),
|
||||
'defaultArchiveURL' => $this->story_post_type->get_archive_link( true ),
|
||||
'cdnURL' => trailingslashit( WEBSTORIES_CDN_URL ),
|
||||
'allowedImageMimeTypes' => $allowed_image_mime_types,
|
||||
'version' => WEBSTORIES_VERSION,
|
||||
'encodeMarkup' => $this->decoder->supports_decoding(),
|
||||
'api' => [
|
||||
'stories' => trailingslashit( $this->story_post_type->get_rest_url() ),
|
||||
'media' => '/web-stories/v1/media/',
|
||||
'currentUser' => '/web-stories/v1/users/me/',
|
||||
'fonts' => trailingslashit( $this->font_post_type->get_rest_url() ),
|
||||
'users' => '/web-stories/v1/users/',
|
||||
'settings' => '/web-stories/v1/settings/',
|
||||
'pages' => '/wp/v2/pages/',
|
||||
'publisherLogos' => '/web-stories/v1/publisher-logos/',
|
||||
'taxonomies' => '/web-stories/v1/taxonomies/',
|
||||
'products' => '/web-stories/v1/products/',
|
||||
],
|
||||
'vendors' => $vendors,
|
||||
'maxUpload' => $max_upload_size,
|
||||
'maxUploadFormatted' => size_format( $max_upload_size ),
|
||||
'editPostsCapabilityName' => $this->story_post_type->get_cap_name( 'edit_posts' ),
|
||||
'capabilities' => [
|
||||
'canManageSettings' => current_user_can( 'manage_options' ),
|
||||
'canUploadFiles' => current_user_can( 'upload_files' ),
|
||||
],
|
||||
'canViewDefaultTemplates' => true,
|
||||
'plugins' => [
|
||||
'siteKit' => $this->site_kit->get_plugin_status(),
|
||||
'woocommerce' => $this->woocommerce->get_plugin_status(),
|
||||
'web-stories' => [
|
||||
'needsUpdate' => $needs_update && ! $auto_updates_enabled,
|
||||
'updateLink' => $can_update ? admin_url( 'plugins.php' ) : null,
|
||||
],
|
||||
],
|
||||
'flags' => array_merge(
|
||||
$this->experiments->get_experiment_statuses( 'general' ),
|
||||
$this->experiments->get_experiment_statuses( 'dashboard' ),
|
||||
),
|
||||
'globalAutoAdvance' => (bool) $auto_advance,
|
||||
'globalPageDuration' => (float) $page_duration,
|
||||
];
|
||||
|
||||
/**
|
||||
* Filters settings passed to the web stories dashboard.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array $settings Array of settings passed to web stories dashboard.
|
||||
*/
|
||||
return apply_filters( 'web_stories_dashboard_settings', $settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a link to the Web Stories dashboard on the WordPress list table view.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function display_link_to_dashboard(): void {
|
||||
if ( ! $this->context->is_story_editor() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'edit' !== $this->context->get_screen_base() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dashboard_url = add_query_arg(
|
||||
[
|
||||
'post_type' => $this->story_post_type->get_slug(),
|
||||
'page' => 'stories-dashboard',
|
||||
],
|
||||
admin_url( 'edit.php' )
|
||||
)
|
||||
?>
|
||||
<div style="margin-top: 20px;">
|
||||
<a href="<?php echo esc_url( $dashboard_url ); ?>">
|
||||
<?php esc_html_e( '← Return to Web Stories Dashboard', 'web-stories' ); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
523
wp-content/plugins/web-stories/includes/Admin/Editor.php
Normal file
523
wp-content/plugins/web-stories/includes/Admin/Editor.php
Normal file
@@ -0,0 +1,523 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Editor
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2020 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Google\Web_Stories\Admin;
|
||||
|
||||
use Google\Web_Stories\Assets;
|
||||
use Google\Web_Stories\Context;
|
||||
use Google\Web_Stories\Decoder;
|
||||
use Google\Web_Stories\Experiments;
|
||||
use Google\Web_Stories\Font_Post_Type;
|
||||
use Google\Web_Stories\Infrastructure\HasRequirements;
|
||||
use Google\Web_Stories\Locale;
|
||||
use Google\Web_Stories\Media\Types;
|
||||
use Google\Web_Stories\Model\Story;
|
||||
use Google\Web_Stories\Page_Template_Post_Type;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
use Google\Web_Stories\Settings;
|
||||
use Google\Web_Stories\Story_Post_Type;
|
||||
use Google\Web_Stories\Tracking;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Class Editor
|
||||
*/
|
||||
class Editor extends Service_Base implements HasRequirements {
|
||||
|
||||
/**
|
||||
* Web Stories editor script handle.
|
||||
*/
|
||||
public const SCRIPT_HANDLE = 'web-stories-editor';
|
||||
|
||||
/**
|
||||
* AMP validator script handle.
|
||||
*/
|
||||
public const AMP_VALIDATOR_SCRIPT_HANDLE = 'amp-validator';
|
||||
|
||||
/**
|
||||
* The libheif script handle.
|
||||
*/
|
||||
public const LIBHEIF_SCRIPT_HANDLE = 'web-stories-libheif';
|
||||
|
||||
/**
|
||||
* Experiments instance.
|
||||
*
|
||||
* @var Experiments Experiments instance.
|
||||
*/
|
||||
private Experiments $experiments;
|
||||
|
||||
/**
|
||||
* Decoder instance.
|
||||
*
|
||||
* @var Decoder Decoder instance.
|
||||
*/
|
||||
private Decoder $decoder;
|
||||
|
||||
/**
|
||||
* Meta boxes instance.
|
||||
*
|
||||
* @var Meta_Boxes Meta_Boxes instance.
|
||||
*/
|
||||
private Meta_Boxes $meta_boxes;
|
||||
|
||||
/**
|
||||
* Locale instance.
|
||||
*
|
||||
* @var Locale Locale instance.
|
||||
*/
|
||||
private Locale $locale;
|
||||
|
||||
/**
|
||||
* Google_Fonts instance.
|
||||
*
|
||||
* @var Google_Fonts Google_Fonts instance.
|
||||
*/
|
||||
private Google_Fonts $google_fonts;
|
||||
|
||||
/**
|
||||
* Assets instance.
|
||||
*
|
||||
* @var Assets Assets instance.
|
||||
*/
|
||||
private Assets $assets;
|
||||
|
||||
/**
|
||||
* Story_Post_Type instance.
|
||||
*
|
||||
* @var Story_Post_Type Story_Post_Type instance.
|
||||
*/
|
||||
private Story_Post_Type $story_post_type;
|
||||
|
||||
/**
|
||||
* Page_Template_Post_Type instance.
|
||||
*
|
||||
* @var Page_Template_Post_Type Page_Template_Post_Type instance.
|
||||
*/
|
||||
private Page_Template_Post_Type $page_template_post_type;
|
||||
|
||||
/**
|
||||
* Font_Post_Type instance.
|
||||
*
|
||||
* @var Font_Post_Type Font_Post_Type instance.
|
||||
*/
|
||||
private Font_Post_Type $font_post_type;
|
||||
|
||||
/**
|
||||
* Context instance.
|
||||
*
|
||||
* @var Context Context instance.
|
||||
*/
|
||||
private Context $context;
|
||||
|
||||
/**
|
||||
* Types instance.
|
||||
*
|
||||
* @var Types Types instance.
|
||||
*/
|
||||
private Types $types;
|
||||
|
||||
/**
|
||||
* Settings instance.
|
||||
*
|
||||
* @var Settings Settings instance.
|
||||
*/
|
||||
private Settings $settings;
|
||||
|
||||
/**
|
||||
* Dashboard constructor.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param Experiments $experiments Experiments instance.
|
||||
* @param Meta_Boxes $meta_boxes Meta_Boxes instance.
|
||||
* @param Decoder $decoder Decoder instance.
|
||||
* @param Locale $locale Locale instance.
|
||||
* @param Google_Fonts $google_fonts Google_Fonts instance.
|
||||
* @param Assets $assets Assets instance.
|
||||
* @param Story_Post_Type $story_post_type Story_Post_Type instance.
|
||||
* @param Page_Template_Post_Type $page_template_post_type Page_Template_Post_Type instance.
|
||||
* @param Font_Post_Type $font_post_type Font_Post_Type instance.
|
||||
* @param Context $context Context instance.
|
||||
* @param Types $types Types instance.
|
||||
* @param Settings $settings Settings instance.
|
||||
*/
|
||||
public function __construct(
|
||||
Experiments $experiments,
|
||||
Meta_Boxes $meta_boxes,
|
||||
Decoder $decoder,
|
||||
Locale $locale,
|
||||
Google_Fonts $google_fonts,
|
||||
Assets $assets,
|
||||
Story_Post_Type $story_post_type,
|
||||
Page_Template_Post_Type $page_template_post_type,
|
||||
Font_Post_Type $font_post_type,
|
||||
Context $context,
|
||||
Types $types,
|
||||
Settings $settings
|
||||
) {
|
||||
$this->experiments = $experiments;
|
||||
$this->meta_boxes = $meta_boxes;
|
||||
$this->decoder = $decoder;
|
||||
$this->locale = $locale;
|
||||
$this->google_fonts = $google_fonts;
|
||||
$this->assets = $assets;
|
||||
$this->story_post_type = $story_post_type;
|
||||
$this->page_template_post_type = $page_template_post_type;
|
||||
$this->font_post_type = $font_post_type;
|
||||
$this->context = $context;
|
||||
$this->types = $types;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Editor logic.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function register(): void {
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
|
||||
add_filter( 'replace_editor', [ $this, 'replace_editor' ], 10, 2 );
|
||||
add_filter( 'use_block_editor_for_post_type', [ $this, 'filter_use_block_editor_for_post_type' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of service IDs required for this service to be registered.
|
||||
*
|
||||
* Needed because the story and page template post types need to be registered first.
|
||||
*
|
||||
* @since 1.14.0
|
||||
*
|
||||
* @return string[] List of required services.
|
||||
*/
|
||||
public static function get_requirements(): array {
|
||||
return [ 'page_template_post_type', 'story_post_type' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace default post editor with our own implementation.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param bool|mixed $replace Bool if to replace editor or not.
|
||||
* @param WP_Post $post Current post object.
|
||||
* @return bool|mixed Whether the editor has been replaced.
|
||||
*/
|
||||
public function replace_editor( $replace, WP_Post $post ) {
|
||||
if ( $this->story_post_type->get_slug() === get_post_type( $post ) ) {
|
||||
|
||||
$script_dependencies = [
|
||||
Tracking::SCRIPT_HANDLE,
|
||||
'postbox',
|
||||
self::AMP_VALIDATOR_SCRIPT_HANDLE,
|
||||
self::LIBHEIF_SCRIPT_HANDLE,
|
||||
];
|
||||
|
||||
// Registering here because the script handle is required for wp_add_inline_script in edit-story.php.
|
||||
$this->assets->register_script_asset( self::SCRIPT_HANDLE, $script_dependencies, false );
|
||||
|
||||
// Since the 'replace_editor' filter can be run multiple times, only load the
|
||||
// custom editor after the 'current_screen' action and when we can be certain the
|
||||
// $post_type, $post_type_object, $post globals are all set by WordPress.
|
||||
if ( isset( $GLOBALS['post'] ) && $post === $GLOBALS['post'] && did_action( 'current_screen' ) ) {
|
||||
require_once WEBSTORIES_PLUGIN_DIR_PATH . 'includes/templates/admin/edit-story.php';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return $replace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters whether post type supports the block editor.
|
||||
*
|
||||
* Disables the block editor and associated logic (like enqueueing assets)
|
||||
* for the story post type.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param bool|mixed $use_block_editor Whether the post type can be edited or not. Default true.
|
||||
* @param string $post_type The post type being checked.
|
||||
* @return false|mixed Whether to use the block editor.
|
||||
*/
|
||||
public function filter_use_block_editor_for_post_type( $use_block_editor, string $post_type ) {
|
||||
if ( $this->story_post_type->get_slug() === $post_type ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $use_block_editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts for the element editor.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $hook The current admin page.
|
||||
*/
|
||||
public function admin_enqueue_scripts( string $hook ): void {
|
||||
if ( ! $this->context->is_story_editor() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only output scripts and styles where in edit screens.
|
||||
if ( ! \in_array( $hook, [ 'post.php', 'post-new.php' ], true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Force media model to load.
|
||||
wp_enqueue_media();
|
||||
|
||||
wp_enqueue_script(
|
||||
self::AMP_VALIDATOR_SCRIPT_HANDLE,
|
||||
'https://cdn.ampproject.org/v0/validator_wasm.js',
|
||||
[],
|
||||
WEBSTORIES_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
|
||||
wp_enqueue_script(
|
||||
self::LIBHEIF_SCRIPT_HANDLE,
|
||||
trailingslashit( WEBSTORIES_CDN_URL ) . 'js/libheif-js@1.14.0/libheif.js',
|
||||
[],
|
||||
WEBSTORIES_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_script( self::SCRIPT_HANDLE );
|
||||
$this->assets->enqueue_style_asset( self::SCRIPT_HANDLE, [ $this->google_fonts::SCRIPT_HANDLE ] );
|
||||
|
||||
wp_localize_script(
|
||||
self::SCRIPT_HANDLE,
|
||||
'webStories',
|
||||
[
|
||||
'publicPath' => $this->assets->get_base_url( 'assets/js/' ), // Required before the editor script is enqueued.
|
||||
'localeData' => $this->assets->get_translations( self::SCRIPT_HANDLE ), // Required for i18n setLocaleData.
|
||||
]
|
||||
);
|
||||
|
||||
// Dequeue forms.css, see https://github.com/googleforcreators/web-stories-wp/issues/349 .
|
||||
$this->assets->remove_admin_style( [ 'forms' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get editor settings as an array.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array<string,mixed> Editor settings.
|
||||
*/
|
||||
public function get_editor_settings(): array {
|
||||
$post = get_post();
|
||||
$story_id = $post->ID ?? null;
|
||||
$general_settings_url = admin_url( 'options-general.php' );
|
||||
|
||||
if ( $story_id ) {
|
||||
$this->setup_lock( $story_id );
|
||||
}
|
||||
|
||||
// Media settings.
|
||||
$max_upload_size = wp_max_upload_size();
|
||||
if ( ! $max_upload_size ) {
|
||||
$max_upload_size = 0;
|
||||
}
|
||||
|
||||
$dashboard_url = add_query_arg(
|
||||
[
|
||||
'post_type' => $this->story_post_type->get_slug(),
|
||||
'page' => 'stories-dashboard',
|
||||
],
|
||||
admin_url( 'edit.php' )
|
||||
);
|
||||
|
||||
$revision_url = admin_url( 'revision.php' );
|
||||
|
||||
$dashboard_settings_url = add_query_arg(
|
||||
[
|
||||
'post_type' => $this->story_post_type->get_slug(),
|
||||
'page' => 'stories-dashboard#/editor-settings',
|
||||
],
|
||||
admin_url( 'edit.php' )
|
||||
);
|
||||
|
||||
$user = wp_get_current_user();
|
||||
|
||||
/** This filter is documented in wp-admin/includes/post.php */
|
||||
$show_locked_dialog = apply_filters( 'show_post_locked_dialog', true, $post, $user );
|
||||
$nonce = wp_create_nonce( 'wp_rest' );
|
||||
|
||||
$story = new Story();
|
||||
$story->load_from_post( $post );
|
||||
|
||||
// Explicitly setting these flags which became the default in PHP 8.1.
|
||||
// Needed for correct single quotes in the editor & output.
|
||||
// See https://github.com/GoogleForCreators/web-stories-wp/issues/10809.
|
||||
$publisher_name = html_entity_decode( $story->get_publisher_name(), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 );
|
||||
|
||||
$shopping_provider = $this->settings->get_setting( $this->settings::SETTING_NAME_SHOPPING_PROVIDER );
|
||||
|
||||
$auto_advance = $this->settings->get_setting( $this->settings::SETTING_NAME_AUTO_ADVANCE );
|
||||
|
||||
$page_duration = $this->settings->get_setting( $this->settings::SETTING_NAME_DEFAULT_PAGE_DURATION );
|
||||
|
||||
$auto_save_link = '';
|
||||
|
||||
if ( isset( $story_id ) ) {
|
||||
|
||||
$auto_save = wp_get_post_autosave( $story_id );
|
||||
|
||||
if ( $auto_save && $post ) {
|
||||
if ( mysql2date( 'U', $auto_save->post_modified_gmt, false ) > mysql2date( 'U', $post->post_modified_gmt, false ) ) {
|
||||
$auto_save_link = get_edit_post_link( $auto_save->ID );
|
||||
} else {
|
||||
wp_delete_post_revision( $auto_save->ID );
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Revision.
|
||||
*
|
||||
* @var int $revision
|
||||
*/
|
||||
$revision = isset( $_GET['revision'] ) ? absint( $_GET['revision'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
$revision_message = ! empty( $revision ) ?
|
||||
sprintf(
|
||||
/* translators: %s: Date and time of the revision. */
|
||||
__( 'Story restored to revision from %s.', 'web-stories' ),
|
||||
wp_post_revision_title( $revision, false )
|
||||
)
|
||||
: false;
|
||||
|
||||
$settings = [
|
||||
'autoSaveInterval' => \defined( 'AUTOSAVE_INTERVAL' ) ? AUTOSAVE_INTERVAL : null,
|
||||
'localAutoSaveInterval' => 15,
|
||||
'autoSaveLink' => $auto_save_link,
|
||||
'isRTL' => is_rtl(),
|
||||
'locale' => $this->locale->get_locale_settings(),
|
||||
'allowedMimeTypes' => $this->types->get_allowed_mime_types(),
|
||||
'postType' => $this->story_post_type->get_slug(),
|
||||
'storyId' => $story_id,
|
||||
'dashboardLink' => $dashboard_url,
|
||||
'revisionLink' => $revision_url,
|
||||
'revisionMessage' => $revision_message,
|
||||
'dashboardSettingsLink' => $dashboard_settings_url,
|
||||
'generalSettingsLink' => $general_settings_url,
|
||||
'cdnURL' => trailingslashit( WEBSTORIES_CDN_URL ),
|
||||
'maxUpload' => $max_upload_size,
|
||||
'editPostsCapabilityName' => $this->story_post_type->get_cap_name( 'edit_posts' ),
|
||||
'capabilities' => [
|
||||
'hasUploadMediaAction' => current_user_can( 'upload_files' ),
|
||||
'canManageSettings' => current_user_can( 'manage_options' ),
|
||||
],
|
||||
'api' => [
|
||||
'users' => '/web-stories/v1/users/',
|
||||
'currentUser' => '/web-stories/v1/users/me/',
|
||||
'stories' => trailingslashit( $this->story_post_type->get_rest_url() ),
|
||||
'pageTemplates' => trailingslashit( $this->page_template_post_type->get_rest_url() ),
|
||||
'media' => '/web-stories/v1/media/',
|
||||
'hotlink' => '/web-stories/v1/hotlink/validate/',
|
||||
'publisherLogos' => '/web-stories/v1/publisher-logos/',
|
||||
'products' => '/web-stories/v1/products/',
|
||||
'proxy' => rest_url( '/web-stories/v1/hotlink/proxy/' ),
|
||||
'link' => '/web-stories/v1/link/',
|
||||
'statusCheck' => '/web-stories/v1/status-check/',
|
||||
'taxonomies' => '/web-stories/v1/taxonomies/',
|
||||
'fonts' => trailingslashit( $this->font_post_type->get_rest_url() ),
|
||||
'metaBoxes' => $this->meta_boxes->get_meta_box_url( (int) $story_id ),
|
||||
'storyLocking' => rest_url( sprintf( '%s/%s/lock/', $this->story_post_type->get_rest_url(), $story_id ) ),
|
||||
],
|
||||
'metadata' => [
|
||||
'publisher' => $publisher_name,
|
||||
],
|
||||
'postLock' => [
|
||||
'interval' => 60,
|
||||
'showLockedDialog' => $show_locked_dialog,
|
||||
],
|
||||
'canViewDefaultTemplates' => true,
|
||||
'version' => WEBSTORIES_VERSION,
|
||||
'nonce' => $nonce,
|
||||
'showMedia3p' => true,
|
||||
'globalAutoAdvance' => (bool) $auto_advance,
|
||||
'globalPageDuration' => (float) $page_duration,
|
||||
'shoppingProvider' => $shopping_provider,
|
||||
'encodeMarkup' => $this->decoder->supports_decoding(),
|
||||
'metaBoxes' => $this->meta_boxes->get_meta_boxes_per_location(),
|
||||
'ffmpegCoreUrl' => trailingslashit( WEBSTORIES_CDN_URL ) . 'js/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js',
|
||||
'mediainfoUrl' => trailingslashit( WEBSTORIES_CDN_URL ) . 'js/mediainfo.js@0.1.9/dist/MediaInfoModule.wasm',
|
||||
'flags' => array_merge(
|
||||
$this->experiments->get_experiment_statuses( 'general' ),
|
||||
$this->experiments->get_experiment_statuses( 'editor' ),
|
||||
),
|
||||
];
|
||||
|
||||
/**
|
||||
* Filters settings passed to the web stories editor.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array $settings Array of settings passed to web stories editor.
|
||||
*/
|
||||
return apply_filters( 'web_stories_editor_settings', $settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup up post lock.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @param int $story_id Post id of story.
|
||||
*/
|
||||
protected function setup_lock( int $story_id ): void {
|
||||
if ( ! $this->story_post_type->has_cap( 'edit_posts' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure these functions are loaded.
|
||||
if (
|
||||
! \function_exists( '\wp_check_post_lock' ) ||
|
||||
! \function_exists( '\wp_set_post_lock' )
|
||||
) {
|
||||
require_once ABSPATH . 'wp-admin/includes/post.php';
|
||||
}
|
||||
|
||||
// Check current lock.
|
||||
$lock_user_id = wp_check_post_lock( $story_id );
|
||||
if ( ! $lock_user_id ) {
|
||||
// If no lock set, create new lock.
|
||||
wp_set_post_lock( $story_id );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Google_Fonts.
|
||||
*
|
||||
* Registers Google fonts for admin screens.
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Google\Web_Stories\Admin;
|
||||
|
||||
use Google\Web_Stories\Infrastructure\Conditional;
|
||||
use Google\Web_Stories\Infrastructure\Registerable;
|
||||
use Google\Web_Stories\Infrastructure\Service;
|
||||
use WP_Styles;
|
||||
|
||||
/**
|
||||
* Class Google_Fonts
|
||||
*
|
||||
* Enqueue Google Fonts stylesheet.
|
||||
*/
|
||||
class Google_Fonts implements Conditional, Service, Registerable {
|
||||
/**
|
||||
* Script handle.
|
||||
*/
|
||||
public const SCRIPT_HANDLE = 'web-stories-fonts';
|
||||
|
||||
/**
|
||||
* Check whether the conditional object is currently needed.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*
|
||||
* @return bool Whether the conditional object is needed.
|
||||
*/
|
||||
public static function is_needed(): bool {
|
||||
return is_admin() && ! wp_doing_ajax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on instantiation.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public function register(): void {
|
||||
add_action( 'wp_default_styles', [ $this, 'register_style' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the google font style.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*
|
||||
* @param WP_Styles $wp_styles WP_Styles instance.
|
||||
*/
|
||||
public function register_style( WP_Styles $wp_styles ): void {
|
||||
// so we need to avoid specifying a version at all.
|
||||
$wp_styles->add(
|
||||
self::SCRIPT_HANDLE,
|
||||
'https://fonts.googleapis.com/css?family=Google+Sans|Google+Sans:b|Google+Sans:500&display=swap',
|
||||
[],
|
||||
WEBSTORIES_VERSION
|
||||
);
|
||||
}
|
||||
}
|
191
wp-content/plugins/web-stories/includes/Admin/Meta_Boxes.php
Normal file
191
wp-content/plugins/web-stories/includes/Admin/Meta_Boxes.php
Normal file
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Meta_Boxes.
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2020 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Google\Web_Stories\Admin;
|
||||
|
||||
use Google\Web_Stories\Service_Base;
|
||||
use Google\Web_Stories\Story_Post_Type;
|
||||
|
||||
/**
|
||||
* Class Meta_Boxes.
|
||||
*/
|
||||
class Meta_Boxes extends Service_Base {
|
||||
/**
|
||||
* Supported meta box locations.
|
||||
*/
|
||||
public const LOCATIONS = [ 'normal', 'advanced', 'side' ];
|
||||
|
||||
/**
|
||||
* Meta box priorities.
|
||||
*/
|
||||
public const PRIORITIES = [ 'high', 'sorted', 'core', 'default', 'low' ];
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public function register(): void {
|
||||
add_action( 'add_meta_boxes_' . Story_Post_Type::POST_TYPE_SLUG, [ $this, 'remove_meta_boxes' ], PHP_INT_MAX );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all meta boxes with '__back_compat_meta_box' set to 'true'.
|
||||
*
|
||||
* This removes things like the post author meta box, as this feature is
|
||||
* already included in the editor.
|
||||
*
|
||||
* Mimics what do_meta_boxes() does for the block editor.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @see do_meta_boxes()
|
||||
*/
|
||||
public function remove_meta_boxes(): void {
|
||||
global $wp_meta_boxes;
|
||||
|
||||
$screen = get_current_screen();
|
||||
|
||||
if ( ! $screen ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( self::LOCATIONS as $context ) {
|
||||
if ( ! isset( $wp_meta_boxes[ $screen->id ][ $context ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( self::PRIORITIES as $priority ) {
|
||||
if ( ! isset( $wp_meta_boxes[ $screen->id ][ $context ][ $priority ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( (array) $wp_meta_boxes[ $screen->id ][ $context ][ $priority ] as $meta_box ) {
|
||||
if ( false === $meta_box || ! $meta_box['title'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
// We don't currently support the 'Custom Fields' meta box.
|
||||
'postcustom' === $meta_box['id'] ||
|
||||
( \is_array( $meta_box['args'] ) && ! empty( $meta_box['args']['__back_compat_meta_box'] ) )
|
||||
) {
|
||||
remove_meta_box( $meta_box['id'], $screen, $context );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the admin URL for handling meta boxes.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param int $story_id Story ID.
|
||||
* @return string Meta box URL.
|
||||
*/
|
||||
public function get_meta_box_url( int $story_id ): string {
|
||||
$meta_box_url = admin_url( 'post.php' );
|
||||
$meta_box_url = add_query_arg(
|
||||
[
|
||||
'post' => $story_id,
|
||||
'action' => 'edit',
|
||||
'meta-box-loader' => true,
|
||||
'meta-box-loader-nonce' => wp_create_nonce( 'meta-box-loader' ),
|
||||
],
|
||||
$meta_box_url
|
||||
);
|
||||
|
||||
return $meta_box_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of custom meta boxes per location.
|
||||
*
|
||||
* Used to disable empty meta boxes in the editor.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @see the_block_editor_meta_boxes()
|
||||
*
|
||||
* @return array<string, array<int, array{id: string, title: string}>> List of meta boxes per location.
|
||||
*/
|
||||
public function get_meta_boxes_per_location(): array {
|
||||
global $wp_meta_boxes;
|
||||
|
||||
$screen = get_current_screen();
|
||||
|
||||
if ( ! $screen ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$_wp_meta_boxes = $wp_meta_boxes ?? [];
|
||||
|
||||
/**
|
||||
* Filters meta box data before making it available to the editor.
|
||||
*
|
||||
* This allows for the modifications of meta boxes that are already
|
||||
* present by this point. Do not use as a means of adding meta box data.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param array $wp_meta_boxes Global meta box state.
|
||||
*/
|
||||
$_wp_meta_boxes = apply_filters( 'web_stories_editor_meta_boxes', $_wp_meta_boxes );
|
||||
|
||||
$meta_boxes_per_location = [];
|
||||
foreach ( self::LOCATIONS as $context ) {
|
||||
$meta_boxes_per_location[ $context ] = [];
|
||||
|
||||
if ( ! isset( $_wp_meta_boxes[ $screen->id ][ $context ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( self::PRIORITIES as $priority ) {
|
||||
if ( ! isset( $_wp_meta_boxes[ $screen->id ][ $context ][ $priority ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$meta_boxes = (array) $_wp_meta_boxes[ $screen->id ][ $context ][ $priority ];
|
||||
foreach ( $meta_boxes as $meta_box ) {
|
||||
if ( false === $meta_box || ! $meta_box['title'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$meta_boxes_per_location[ $context ][] = [
|
||||
'id' => $meta_box['id'],
|
||||
'title' => $meta_box['title'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $meta_boxes_per_location;
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* PluginActionLinks class.
|
||||
*
|
||||
* Updates the plugin action links for the plugin.
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Google\Web_Stories\Admin;
|
||||
|
||||
use Google\Web_Stories\Service_Base;
|
||||
use Google\Web_Stories\Story_Post_Type;
|
||||
|
||||
/**
|
||||
* Updates the plugin action links for the plugin.
|
||||
*/
|
||||
class PluginActionLinks extends Service_Base {
|
||||
/**
|
||||
* Runs on instantiation.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public function register(): void {
|
||||
$basename = plugin_basename( WEBSTORIES_PLUGIN_FILE );
|
||||
add_filter( 'plugin_action_links_' . $basename, [ $this, 'action_links' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action to use for registering the service.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @return string Registration action to use.
|
||||
*/
|
||||
public static function get_registration_action(): string {
|
||||
return 'admin_init';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add action link to plugin settings page.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param array|mixed $links Plugin action links.
|
||||
* @return array|mixed
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($links is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function action_links( $links ) {
|
||||
if ( ! \is_array( $links ) ) {
|
||||
return $links;
|
||||
}
|
||||
$slug = sprintf( 'edit.php?post_type=%s&page=stories-dashboard#/editor-settings', Story_Post_Type::POST_TYPE_SLUG );
|
||||
$url = get_admin_url( null, $slug );
|
||||
$links[] = sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
esc_url( $url ),
|
||||
esc_html__( 'Settings', 'web-stories' )
|
||||
);
|
||||
|
||||
return $links;
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* PluginRowMeta class.
|
||||
*
|
||||
* Updates the plugin row meta for the plugin.
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Google\Web_Stories\Admin;
|
||||
|
||||
use Google\Web_Stories\Service_Base;
|
||||
|
||||
/**
|
||||
* Updates the plugin row meta for the plugin.
|
||||
*/
|
||||
class PluginRowMeta extends Service_Base {
|
||||
|
||||
/**
|
||||
* Runs on instantiation.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public function register(): void {
|
||||
add_filter( 'plugin_row_meta', [ $this, 'get_plugin_row_meta' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action to use for registering the service.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @return string Registration action to use.
|
||||
*/
|
||||
public static function get_registration_action(): string {
|
||||
return 'admin_init';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the plugin row meta with links to review plugin and get support.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param string[]|mixed $meta An array of the plugin's metadata, including the version, author, author URI,
|
||||
* and plugin URI.
|
||||
* @param string $plugin_file Path to the plugin file relative to the plugins directory.
|
||||
* @return string[]|mixed Plugin row meta.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($meta is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function get_plugin_row_meta( $meta, string $plugin_file ) {
|
||||
if ( plugin_basename( WEBSTORIES_PLUGIN_FILE ) !== $plugin_file ) {
|
||||
return $meta;
|
||||
}
|
||||
if ( ! \is_array( $meta ) ) {
|
||||
return $meta;
|
||||
}
|
||||
$additional_meta = [
|
||||
'<a href="https://wordpress.org/support/plugin/web-stories/" target="_blank" rel="noreferrer noopener">' . esc_html__( 'Contact support', 'web-stories' ) . '</a>',
|
||||
'<a href="https://wordpress.org/support/plugin/web-stories/reviews/#new-post" target="_blank" rel="noreferrer noopener">' . esc_html__( 'Leave review', 'web-stories' ) . '</a>',
|
||||
];
|
||||
|
||||
return [ ...$meta, ...$additional_meta ];
|
||||
}
|
||||
}
|
259
wp-content/plugins/web-stories/includes/Admin/Site_Health.php
Normal file
259
wp-content/plugins/web-stories/includes/Admin/Site_Health.php
Normal file
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
/**
|
||||
* Site Health Class.
|
||||
*
|
||||
* Adds tests and debugging information for Site Health.
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Google\Web_Stories\Admin;
|
||||
|
||||
use Google\Web_Stories\Experiments;
|
||||
use Google\Web_Stories\Infrastructure\Conditional;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
|
||||
/**
|
||||
* Class Site_Health
|
||||
*/
|
||||
class Site_Health extends Service_Base implements Conditional {
|
||||
/**
|
||||
* Experiments instance.
|
||||
*
|
||||
* @var Experiments Experiments instance.
|
||||
*/
|
||||
private Experiments $experiments;
|
||||
|
||||
/**
|
||||
* Site_Health constructor.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*
|
||||
* @param Experiments $experiments Experiments instance.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( Experiments $experiments ) {
|
||||
$this->experiments = $experiments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the conditional object is currently needed.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*
|
||||
* @return bool Whether the conditional object is needed.
|
||||
*/
|
||||
public static function is_needed(): bool {
|
||||
return is_admin() && ! wp_doing_ajax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action to use for registering the service.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*
|
||||
* @return string Registration action to use.
|
||||
*/
|
||||
public static function get_registration_action(): string {
|
||||
return 'wp_loaded';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the filters.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public function register(): void {
|
||||
add_filter( 'debug_information', [ $this, 'add_debug_information' ] );
|
||||
add_filter( 'site_status_test_php_modules', [ $this, 'add_extensions' ] );
|
||||
add_filter( 'site_status_test_result', [ $this, 'modify_test_result' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds debug information for Web stories.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*
|
||||
* @param array|mixed $debugging_information The debugging information from Core.
|
||||
* @return array|mixed The debugging information, with added information for Web stories.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($debugging_information is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function add_debug_information( $debugging_information ) {
|
||||
$enabled_experiments = [];
|
||||
foreach ( $this->experiments->get_experiments() as $experiment ) {
|
||||
$enabled = $this->experiments->is_experiment_enabled( $experiment['name'] );
|
||||
if ( $enabled ) {
|
||||
$enabled_experiments[ $experiment['label'] ] = $this->get_formatted_output( $enabled );
|
||||
}
|
||||
}
|
||||
if ( ! $enabled_experiments ) {
|
||||
$enabled_experiments = __( 'No experiments enabled', 'web-stories' );
|
||||
}
|
||||
if ( ! \is_array( $debugging_information ) ) {
|
||||
return $debugging_information;
|
||||
}
|
||||
$extra_data = [
|
||||
'web_stories' => [
|
||||
'label' => esc_html__( 'Web Stories', 'web-stories' ),
|
||||
'description' => esc_html__( 'Debugging information for the Web Stories plugin.', 'web-stories' ),
|
||||
'fields' => [
|
||||
'web_stories_version' => [
|
||||
'label' => 'WEBSTORIES_VERSION',
|
||||
'value' => WEBSTORIES_VERSION,
|
||||
'private' => false,
|
||||
],
|
||||
'web_stories_db_version' => [
|
||||
'label' => 'WEBSTORIES_DB_VERSION',
|
||||
'value' => WEBSTORIES_DB_VERSION,
|
||||
'private' => false,
|
||||
],
|
||||
'web_stories_amp_version' => [
|
||||
'label' => 'WEBSTORIES_AMP_VERSION',
|
||||
'value' => WEBSTORIES_AMP_VERSION,
|
||||
'private' => false,
|
||||
],
|
||||
'web_stories_cdn_url' => [
|
||||
'label' => 'WEBSTORIES_CDN_URL',
|
||||
'value' => WEBSTORIES_CDN_URL,
|
||||
'private' => false,
|
||||
],
|
||||
'web_stories_dev_mode' => [
|
||||
'label' => 'WEBSTORIES_DEV_MODE',
|
||||
'private' => false,
|
||||
'value' => $this->get_formatted_output( WEBSTORIES_DEV_MODE ),
|
||||
'debug' => WEBSTORIES_DEV_MODE,
|
||||
],
|
||||
'web_stories_theme_support' => [
|
||||
'label' => 'Theme supports',
|
||||
'value' => $this->get_formatted_output( current_theme_supports( 'web-stories' ) ),
|
||||
'private' => false,
|
||||
],
|
||||
'web_stories_enabled_experiments' => [
|
||||
'label' => 'Experiments',
|
||||
'value' => $enabled_experiments,
|
||||
'private' => false,
|
||||
],
|
||||
'web_stories_libxml_version' => [
|
||||
'label' => 'libxml Version',
|
||||
'value' => LIBXML_DOTTED_VERSION,
|
||||
'private' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return array_merge( $debugging_information, $extra_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds suggested PHP extensions to those that Core depends on.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*
|
||||
* @param array|mixed $core_extensions The existing extensions from Core.
|
||||
* @return array|mixed The extensions, including those for Web Stories.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($core_extensions is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function add_extensions( $core_extensions ) {
|
||||
if ( ! \is_array( $core_extensions ) ) {
|
||||
return $core_extensions;
|
||||
}
|
||||
$extensions = [
|
||||
'json' => [
|
||||
'extension' => 'json',
|
||||
'function' => 'json_encode',
|
||||
'required' => false,
|
||||
],
|
||||
'libxml' => [
|
||||
'extension' => 'libxml',
|
||||
'function' => 'libxml_use_internal_errors',
|
||||
'required' => false,
|
||||
|
||||
],
|
||||
'date' => [
|
||||
'extension' => 'date',
|
||||
'class' => 'DateTimeImmutable',
|
||||
'required' => false,
|
||||
],
|
||||
'dom' => [
|
||||
'extension' => 'dom',
|
||||
'class' => 'DOMNode',
|
||||
'required' => false,
|
||||
],
|
||||
'mbstring' => [
|
||||
'extension' => 'mbstring',
|
||||
'required' => false,
|
||||
],
|
||||
'spl' => [
|
||||
'function' => 'spl_autoload_register',
|
||||
'required' => false,
|
||||
],
|
||||
];
|
||||
|
||||
return array_merge( $core_extensions, $extensions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify test results.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*
|
||||
* @param array|mixed $test_result Site Health test result.
|
||||
* @return array|mixed Modified test result.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($test_result is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function modify_test_result( $test_result ) {
|
||||
if ( ! \is_array( $test_result ) ) {
|
||||
return $test_result;
|
||||
}
|
||||
// Set the `https_status` test status to critical if its current status is recommended, along with adding to the
|
||||
// description for why its required for Web Stories.
|
||||
if ( isset( $test_result['test'], $test_result['status'], $test_result['description'] ) && 'https_status' === $test_result['test'] && 'recommended' === $test_result['status'] ) {
|
||||
$test_result['status'] = 'critical';
|
||||
$test_result['description'] .= '<p>' . __( 'Additionally, Web Stories requires HTTPS for most components to work properly, including iframes and videos.', 'web-stories' ) . '</p>';
|
||||
}
|
||||
|
||||
return $test_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the value as enabled or disabled.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*
|
||||
* @param mixed $value Value to formatted.
|
||||
*/
|
||||
protected function get_formatted_output( $value ): string {
|
||||
return $value ? __( 'Enabled', 'web-stories' ) : __( 'Disabled', 'web-stories' );
|
||||
}
|
||||
}
|
216
wp-content/plugins/web-stories/includes/Admin/TinyMCE.php
Normal file
216
wp-content/plugins/web-stories/includes/Admin/TinyMCE.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
/**
|
||||
* TinyMCE Class.
|
||||
*
|
||||
* Necessary operations for classic editor compatibility.
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Google\Web_Stories\Admin;
|
||||
|
||||
use Google\Web_Stories\Assets;
|
||||
use Google\Web_Stories\Context;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
use Google\Web_Stories\Stories_Script_Data;
|
||||
|
||||
/**
|
||||
* Class TinyMCE
|
||||
*/
|
||||
class TinyMCE extends Service_Base {
|
||||
/**
|
||||
* Web Stories tinymce script handle.
|
||||
*/
|
||||
public const SCRIPT_HANDLE = 'web-stories-tinymce-button';
|
||||
|
||||
/**
|
||||
* Assets instance.
|
||||
*
|
||||
* @var Assets Assets instance.
|
||||
*/
|
||||
private Assets $assets;
|
||||
|
||||
/**
|
||||
* Stories_Script_Data instance.
|
||||
*
|
||||
* @var Stories_Script_Data Stories_Script_Data instance.
|
||||
*/
|
||||
protected Stories_Script_Data $stories_script_data;
|
||||
|
||||
/**
|
||||
* Context instance.
|
||||
*
|
||||
* @var Context Context instance.
|
||||
*/
|
||||
private Context $context;
|
||||
|
||||
/**
|
||||
* Tinymce constructor.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*
|
||||
* @param Assets $assets Assets instance.
|
||||
* @param Stories_Script_Data $stories_script_data Stories_Script_Data instance.
|
||||
* @param Context $context Context instance.
|
||||
*/
|
||||
public function __construct( Assets $assets, Stories_Script_Data $stories_script_data, Context $context ) {
|
||||
$this->assets = $assets;
|
||||
$this->stories_script_data = $stories_script_data;
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization actions.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public function register(): void {
|
||||
if ( $this->context->is_block_editor() || $this->context->is_story_editor() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->register_assets();
|
||||
|
||||
add_action( 'wp_enqueue_editor', [ $this, 'enqueue_assets' ] );
|
||||
add_filter( 'mce_buttons', [ $this, 'tinymce_web_stories_button' ] );
|
||||
add_filter( 'mce_external_plugins', [ $this, 'web_stories_mce_plugin' ] );
|
||||
add_action( 'admin_footer', [ $this, 'web_stories_tinymce_root_element' ] );
|
||||
add_filter( 'script_loader_tag', [ $this, 'script_loader_tag' ], 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action to use for registering the service.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @return string Registration action to use.
|
||||
*/
|
||||
public static function get_registration_action(): string {
|
||||
return 'admin_enqueue_scripts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add web stories button in TinyMCE editor.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @param array|mixed $buttons Array of TinyMCE buttons.
|
||||
* @return array|mixed
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($buttons is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function tinymce_web_stories_button( $buttons ) {
|
||||
if ( ! \is_array( $buttons ) ) {
|
||||
return $buttons;
|
||||
}
|
||||
$buttons[] = 'web_stories';
|
||||
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register web stories plugin for tinycemce editor.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @param array|mixed $plugins Array of TinyMCE plugin scripts.
|
||||
* @return array|mixed
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($plugins is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function web_stories_mce_plugin( $plugins ) {
|
||||
if ( ! \is_array( $plugins ) ) {
|
||||
return $plugins;
|
||||
}
|
||||
$plugins['web_stories'] = $this->assets->get_base_url( sprintf( 'assets/js/%s.js', self::SCRIPT_HANDLE ) );
|
||||
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue related scripts.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public function register_assets(): void {
|
||||
$this->assets->enqueue_style( 'wp-components' );
|
||||
|
||||
$this->assets->enqueue_script_asset( self::SCRIPT_HANDLE );
|
||||
wp_localize_script(
|
||||
self::SCRIPT_HANDLE,
|
||||
'webStoriesData',
|
||||
$this->stories_script_data->get_script_data()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hijack the button's script to render an empty script tag.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @param string|mixed $tag The `<script>` tag for the enqueued script.
|
||||
* @param string $handle The script's registered handle.
|
||||
* @param string $src The script's source URL.
|
||||
* @return string|mixed The filtered script tag.
|
||||
*/
|
||||
public function script_loader_tag( $tag, string $handle, string $src ) {
|
||||
if ( ! \is_string( $tag ) ) {
|
||||
return $tag;
|
||||
}
|
||||
|
||||
if ( self::SCRIPT_HANDLE === $handle ) {
|
||||
$tag = str_replace( $src, '', $tag );
|
||||
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- False positive.
|
||||
$tag = (string) preg_replace( '#<script src=\'\'(.*?)>(.*?)</script>#is', '', $tag );
|
||||
}
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue related scripts.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public function enqueue_assets(): void {
|
||||
$this->assets->enqueue_style( 'wp-components' );
|
||||
$this->assets->enqueue_script_asset( self::SCRIPT_HANDLE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Root element for tinymce editor.
|
||||
* This is useful for performing some react operations.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public function web_stories_tinymce_root_element(): void {
|
||||
?>
|
||||
<div id="web-stories-tinymce"></div>
|
||||
<?php
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user