Initial commit
This commit is contained in:
commit
28e6ddf404
1083 changed files with 191734 additions and 0 deletions
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
// Shared logic between Jetpack admin pages
|
||||
abstract class Jetpack_Admin_Page {
|
||||
// Add page specific actions given the page hook
|
||||
abstract function add_page_actions( $hook );
|
||||
|
||||
// Create a menu item for the page and returns the hook
|
||||
abstract function get_page_hook();
|
||||
|
||||
// Enqueue and localize page specific scripts
|
||||
abstract function page_admin_scripts();
|
||||
|
||||
// Render page specific HTML
|
||||
abstract function page_render();
|
||||
|
||||
function __construct() {
|
||||
$this->jetpack = Jetpack::init();
|
||||
}
|
||||
|
||||
function add_actions() {
|
||||
/**
|
||||
* Don't add in the modules page unless modules are available!
|
||||
*/
|
||||
if ( $this->dont_show_if_not_active && ! Jetpack::is_active() && ! Jetpack::is_development_mode() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize menu item for the page in the admin
|
||||
$hook = $this->get_page_hook();
|
||||
|
||||
// Attach hooks common to all Jetpack admin pages based on the created
|
||||
// hook
|
||||
add_action( "load-$hook", array( $this, 'admin_help' ) );
|
||||
add_action( "load-$hook", array( $this, 'admin_page_load' ) );
|
||||
add_action( "admin_head-$hook", array( $this, 'admin_head' ) );
|
||||
|
||||
add_action( "admin_footer-$hook", array( $this, 'module_modal_js_template' ) );
|
||||
|
||||
add_action( "admin_print_styles-$hook", array( $this, 'admin_styles' ) );
|
||||
add_action( "admin_print_scripts-$hook", array( $this, 'admin_scripts' ) );
|
||||
|
||||
// Attach page specific actions in addition to the above
|
||||
$this->add_page_actions( $hook );
|
||||
}
|
||||
|
||||
function admin_head() {
|
||||
if ( isset( $_GET['configure'] ) && Jetpack::is_module( $_GET['configure'] ) && current_user_can( 'manage_options' ) ) {
|
||||
/**
|
||||
* Fires in the <head> of a particular Jetpack configuation page.
|
||||
*
|
||||
* The dynamic portion of the hook name, `$_GET['configure']`,
|
||||
* refers to the slug of module, such as 'stats', 'sso', etc.
|
||||
* A complete hook for the latter would be
|
||||
* 'jetpack_module_configuation_head_sso'.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
do_action( 'jetpack_module_configuration_head_' . $_GET['configure'] );
|
||||
}
|
||||
}
|
||||
|
||||
// Render the page with a common top and bottom part, and page specific
|
||||
// content
|
||||
function render() {
|
||||
$this->admin_page_top();
|
||||
$this->page_render();
|
||||
$this->admin_page_bottom();
|
||||
}
|
||||
|
||||
function admin_help() {
|
||||
$this->jetpack->admin_help();
|
||||
}
|
||||
|
||||
function admin_page_load() {
|
||||
// This is big. For the moment, just call the existing one.
|
||||
$this->jetpack->admin_page_load();
|
||||
}
|
||||
|
||||
// Load underscore template for the landing page and settings page modal
|
||||
function module_modal_js_template() {
|
||||
Jetpack::init()->load_view( 'admin/module-modal-template.php' );
|
||||
}
|
||||
|
||||
function admin_page_top() {
|
||||
include_once( JETPACK__PLUGIN_DIR . '_inc/header.php' );
|
||||
}
|
||||
|
||||
function admin_page_bottom() {
|
||||
include_once( JETPACK__PLUGIN_DIR . '_inc/footer.php' );
|
||||
}
|
||||
|
||||
// Add page specific scripts and jetpack stats for all menu pages
|
||||
function admin_scripts() {
|
||||
$this->page_admin_scripts(); // Delegate to inheriting class
|
||||
add_action( 'admin_footer', array( $this->jetpack, 'do_stats' ) );
|
||||
}
|
||||
|
||||
// Enqueue the Jetpack admin stylesheet
|
||||
function admin_styles() {
|
||||
$min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
|
||||
|
||||
wp_enqueue_style( 'jetpack-google-fonts', '//fonts.googleapis.com/css?family=Open+Sans:400italic,400,700,600,800' );
|
||||
|
||||
wp_enqueue_style( 'jetpack-admin', plugins_url( "css/jetpack-admin{$min}.css", JETPACK__PLUGIN_FILE ), array( 'genericons' ), JETPACK__VERSION . '-20121016' );
|
||||
wp_style_add_data( 'jetpack-admin', 'rtl', 'replace' );
|
||||
wp_style_add_data( 'jetpack-admin', 'suffix', $min );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,299 @@
|
|||
<?php
|
||||
include_once( 'class.jetpack-admin-page.php' );
|
||||
|
||||
// Builds the landing page and its menu
|
||||
class Jetpack_Landing_Page extends Jetpack_Admin_Page {
|
||||
protected $dont_show_if_not_active = false;
|
||||
|
||||
function get_page_hook() {
|
||||
$title = _x( 'Jetpack', 'The menu item label', 'jetpack' );
|
||||
|
||||
list( $jetpack_version ) = explode( ':', Jetpack_Options::get_option( 'version' ) );
|
||||
if (
|
||||
$jetpack_version
|
||||
&&
|
||||
$jetpack_version != JETPACK__VERSION
|
||||
&&
|
||||
( $new_modules = Jetpack::get_default_modules( $jetpack_version, JETPACK__VERSION ) )
|
||||
&&
|
||||
is_array( $new_modules )
|
||||
&&
|
||||
( $new_modules_count = count( $new_modules ) )
|
||||
&&
|
||||
( Jetpack::is_active() || Jetpack::is_development_mode() )
|
||||
) {
|
||||
$new_count_i18n = number_format_i18n( $new_modules_count );
|
||||
$span_title = esc_attr( sprintf( _n( 'One New Jetpack Module', '%s New Jetpack Modules', $new_modules_count, 'jetpack' ), $new_count_i18n ) );
|
||||
$format = _x( 'Jetpack %s', 'The menu item label with a new module count as %s', 'jetpack' );
|
||||
$update_markup = "<span class='update-plugins count-{$new_modules_count}' title='$span_title'><span class='update-count'>$new_count_i18n</span></span>";
|
||||
$title = sprintf( $format, $update_markup );
|
||||
}
|
||||
|
||||
// Add the main admin Jetpack menu with possible information about new
|
||||
// modules
|
||||
add_menu_page( 'Jetpack', $title, 'jetpack_admin_page', 'jetpack', array( $this, 'render' ), 'div' );
|
||||
// also create the submenu
|
||||
return add_submenu_page( 'jetpack', $title, $title, 'jetpack_admin_page', 'jetpack' );
|
||||
}
|
||||
|
||||
function add_page_actions( $hook ) {
|
||||
// Add landing page specific underscore templates
|
||||
/**
|
||||
* Filters the js_templates callback value
|
||||
*
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @param array array( $this, 'js_templates' ) js_templates callback.
|
||||
* @param string $hook Specific admin page.
|
||||
*/
|
||||
add_action( "admin_footer-$hook", apply_filters( 'jetpack_landing_page_js_templates_callback', array( $this, 'js_templates' ), $hook ) );
|
||||
/** This action is documented in class.jetpack.php */
|
||||
do_action( 'jetpack_admin_menu', $hook );
|
||||
|
||||
// Place the Jetpack menu item on top and others in the order they
|
||||
// appear
|
||||
add_filter( 'custom_menu_order', '__return_true' );
|
||||
add_filter( 'menu_order', array( $this, 'jetpack_menu_order' ) );
|
||||
|
||||
add_action( 'jetpack_notices_update_settings', array( $this, 'show_notices_update_settings' ), 10, 1 );
|
||||
}
|
||||
|
||||
/*
|
||||
* Build an array of a specific module tag.
|
||||
*
|
||||
* @param string Name of the module tag
|
||||
* @return array The module slug, config url, and name of each Jump Start module
|
||||
*/
|
||||
function jumpstart_module_tag( $tag ) {
|
||||
$modules = Jetpack_Admin::init()->get_modules();
|
||||
|
||||
$module_info = array();
|
||||
foreach ( $modules as $module => $value ) {
|
||||
if ( in_array( $tag, $value['feature'] ) ) {
|
||||
$module_info[] = array(
|
||||
'module_slug' => $value['module'],
|
||||
'module_name' => $value['name'],
|
||||
'configure_url' => $value['configure_url'],
|
||||
);
|
||||
}
|
||||
}
|
||||
return $module_info;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only show Jump Start on first activation.
|
||||
* Any option 'jumpstart' other than 'new connection' will hide it.
|
||||
*
|
||||
* The option can be of 4 things, and will be stored as such:
|
||||
* new_connection : Brand new connection - Show
|
||||
* jumpstart_activated : Jump Start has been activated - dismiss
|
||||
* jetpack_action_taken: Manual activation of a module already happened - dismiss
|
||||
* jumpstart_dismissed : Manual dismissal of Jump Start - dismiss
|
||||
*
|
||||
* @return bool | show or hide
|
||||
*/
|
||||
function jetpack_show_jumpstart() {
|
||||
$jumpstart_option = Jetpack_Options::get_option( 'jumpstart' );
|
||||
|
||||
$hide_options = array(
|
||||
'jumpstart_activated',
|
||||
'jetpack_action_taken',
|
||||
'jumpstart_dismissed'
|
||||
);
|
||||
|
||||
if ( ! $jumpstart_option || in_array( $jumpstart_option, $hide_options ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* List of recommended modules for the Jump Start paragraph text.
|
||||
* Will only show up in the paragraph if they are not active.
|
||||
*
|
||||
* @return string | comma-separated recommended modules that are not active
|
||||
*/
|
||||
function jumpstart_list_modules() {
|
||||
$jumpstart_recommended = $this->jumpstart_module_tag( 'Jumpstart' );
|
||||
|
||||
$module_name = array();
|
||||
foreach ( $jumpstart_recommended as $module => $val ) {
|
||||
if ( ! Jetpack::is_module_active( $val['module_slug'] ) ) {
|
||||
$module_name[] = $val['module_name'];
|
||||
}
|
||||
}
|
||||
|
||||
return $module_name;
|
||||
}
|
||||
|
||||
function jetpack_menu_order( $menu_order ) {
|
||||
$jp_menu_order = array();
|
||||
|
||||
foreach ( $menu_order as $index => $item ) {
|
||||
if ( $item != 'jetpack' )
|
||||
$jp_menu_order[] = $item;
|
||||
|
||||
if ( $index == 0 )
|
||||
$jp_menu_order[] = 'jetpack';
|
||||
}
|
||||
|
||||
return $jp_menu_order;
|
||||
}
|
||||
|
||||
function js_templates() {
|
||||
Jetpack::init()->load_view( 'admin/landing-page-templates.php' );
|
||||
}
|
||||
|
||||
function page_render() {
|
||||
// Handle redirects to configuration pages
|
||||
if ( ! empty( $_GET['configure'] ) ) {
|
||||
return $this->render_nojs_configurable();
|
||||
}
|
||||
|
||||
global $current_user;
|
||||
|
||||
$is_connected = Jetpack::is_active();
|
||||
$user_token = Jetpack_Data::get_access_token( $current_user->ID );
|
||||
$is_user_connected = $user_token && ! is_wp_error( $user_token );
|
||||
$is_master_user = $current_user->ID == Jetpack_Options::get_option( 'master_user' );
|
||||
|
||||
if ( Jetpack::is_development_mode() ) {
|
||||
$is_connected = true;
|
||||
$is_user_connected = true;
|
||||
$is_master_user = false;
|
||||
}
|
||||
|
||||
// Set template data for the admin page template
|
||||
$data = array(
|
||||
'is_connected' => $is_connected,
|
||||
'is_user_connected' => $is_user_connected,
|
||||
'is_master_user' => $is_master_user,
|
||||
'show_jumpstart' => $this->jetpack_show_jumpstart(),
|
||||
'jumpstart_list' => $this->jumpstart_list_modules(),
|
||||
'recommended_list' => $this->jumpstart_module_tag( 'Recommended' ),
|
||||
);
|
||||
Jetpack::init()->load_view( 'admin/admin-page.php', $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a notice message to users after they save Module config settings
|
||||
* @param string $module_id
|
||||
* @return null
|
||||
*/
|
||||
function show_notices_update_settings( $module_id ) {
|
||||
$state = Jetpack::state( 'message' );
|
||||
|
||||
switch( $state ) {
|
||||
case 'module_activated' :
|
||||
if ( $module = Jetpack::get_module( Jetpack::state( 'module' ) ) ) {
|
||||
$message = sprintf( __( '<strong>%s Activated!</strong> You can change the setting of it here.', 'jetpack' ), $module['name'] );
|
||||
}
|
||||
break;
|
||||
case 'module_configured':
|
||||
$message = __( '<strong>Module settings were saved.</strong> ', 'jetpack' );
|
||||
break;
|
||||
case 'no_message' :
|
||||
break;
|
||||
}
|
||||
|
||||
if ( isset( $message ) ) {
|
||||
?>
|
||||
<div id="message" class="jetpack-message">
|
||||
<div class="squeezer">
|
||||
<h2><?php echo wp_kses( $message, array( 'strong' => array(), 'a' => array( 'href' => true ), 'br' => true ) ); ?></h2>
|
||||
<?php
|
||||
/**
|
||||
* Fires within the displayed message when a feature configuation is updated.
|
||||
*
|
||||
* This is a dynamic hook with `$module_id` being the slug of the module being updated.
|
||||
*
|
||||
* @since 3.4.0
|
||||
*/
|
||||
do_action( 'jetpack_notices_update_settings_' . $module_id ); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
add_action( 'jetpack_notices', array( Jetpack::init(), 'admin_notices' ) );
|
||||
}
|
||||
|
||||
// Render the configuration page for the module if it exists and an error
|
||||
// screen if the module is not configurable
|
||||
function render_nojs_configurable() {
|
||||
echo '<div class="clouds-sm"></div>';
|
||||
echo '<div class="wrap configure-module">';
|
||||
|
||||
$module_name = preg_replace( '/[^\da-z\-]+/', '', $_GET['configure'] );
|
||||
if ( Jetpack::is_module( $module_name ) && current_user_can( 'jetpack_configure_modules' ) ) {
|
||||
Jetpack::admin_screen_configure_module( $module_name );
|
||||
} else {
|
||||
echo '<h2>' . esc_html__( 'Error, bad module.', 'jetpack' ) . '</h2>';
|
||||
}
|
||||
|
||||
echo '</div><!-- /wrap -->';
|
||||
}
|
||||
|
||||
/*
|
||||
* Build an array of Jump Start stats urls.
|
||||
* requires the build URL args passed as an array
|
||||
*
|
||||
* @param array $jumpstart_stats
|
||||
* @return (array) of built stats urls
|
||||
*/
|
||||
function build_jumpstart_stats_urls( $jumpstart_stats ) {
|
||||
$jumpstart_urls = array();
|
||||
|
||||
foreach ( $jumpstart_stats as $value) {
|
||||
$jumpstart_urls[$value] = Jetpack::build_stats_url( array( 'x_jetpack-jumpstart' => $value ) );
|
||||
}
|
||||
|
||||
return $jumpstart_urls;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Build an array of NUX admin stats urls.
|
||||
* requires the build URL args passed as an array
|
||||
*
|
||||
* @param array $nux_admin_stats
|
||||
* @return (array) of built stats urls
|
||||
*/
|
||||
function build_nux_admin_stats_urls( $nux_admin_stats ) {
|
||||
$nux_admin_urls = array();
|
||||
|
||||
foreach ( $nux_admin_stats as $value) {
|
||||
$nux_admin_urls[ $value ] = Jetpack::build_stats_url( array( 'x_jetpack-nux' => $value ) );
|
||||
}
|
||||
|
||||
return $nux_admin_urls;
|
||||
|
||||
}
|
||||
|
||||
function page_admin_scripts() {
|
||||
// Enqueue jp.js and localize it
|
||||
wp_enqueue_script( 'jetpack-js', plugins_url( '_inc/jp.js', JETPACK__PLUGIN_FILE ),
|
||||
array( 'jquery', 'wp-util' ), JETPACK__VERSION . '-20121111' );
|
||||
wp_localize_script(
|
||||
'jetpack-js',
|
||||
'jetpackL10n',
|
||||
array(
|
||||
'ays_disconnect' => __( "This will deactivate all Jetpack modules.\nAre you sure you want to disconnect?", 'jetpack' ),
|
||||
'ays_unlink' => __( "This will prevent user-specific modules such as Publicize, Notifications and Post By Email from working.\nAre you sure you want to unlink?", 'jetpack' ),
|
||||
'ays_dismiss' => __( "This will deactivate Jetpack.\nAre you sure you want to deactivate Jetpack?", 'jetpack' ),
|
||||
'view_all_features' => __( 'View all Jetpack features', 'jetpack' ),
|
||||
'no_modules_found' => sprintf( __( 'Sorry, no modules were found for the search term "%s"', 'jetpack' ), '{term}' ),
|
||||
'modules' => Jetpack::get_translated_modules( array_values( Jetpack_Admin::init()->get_modules() ) ),
|
||||
'currentVersion' => JETPACK__VERSION,
|
||||
'ajaxurl' => admin_url( 'admin-ajax.php' ),
|
||||
'jumpstart_modules' => $this->jumpstart_module_tag( 'Jumpstart' ),
|
||||
'show_jumpstart' => $this->jetpack_show_jumpstart(),
|
||||
'activate_nonce' => wp_create_nonce( 'jetpack-jumpstart-nonce' ),
|
||||
'admin_nonce' => wp_create_nonce( 'jetpack-admin-nonce' ),
|
||||
'jumpstart_stats_urls' => $this->build_jumpstart_stats_urls( array( 'dismiss', 'jumpstarted', 'learnmore', 'viewed', 'manual' ) ),
|
||||
'admin_stats_urls' => $this->build_nux_admin_stats_urls( array( 'enabled', 'deactivated', 'learnmore' ) ),
|
||||
'site_url_manage' => Jetpack::build_raw_urls( get_site_url() ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
include_once( 'class.jetpack-admin-page.php' );
|
||||
include_once( JETPACK__PLUGIN_DIR . 'class.jetpack-modules-list-table.php' );
|
||||
|
||||
// Builds the My Jetpack page
|
||||
class Jetpack_My_Jetpack_Page extends Jetpack_Admin_Page {
|
||||
// Show the settings page only when Jetpack is connected or in dev mode
|
||||
protected $dont_show_if_not_active = true;
|
||||
function add_page_actions( $hook ) {} // There are no page specific actions to attach to the menu
|
||||
|
||||
// Adds the My Jetpack page, but hides it from the submenu
|
||||
function get_page_hook() {
|
||||
return add_submenu_page( null, __( 'My Jetpack', 'jetpack' ), __( 'My Jetpack', 'jetpack' ), 'jetpack_connect_user', 'my_jetpack', array( $this, 'render' ) );
|
||||
}
|
||||
|
||||
// Renders the view file
|
||||
function page_render() {
|
||||
Jetpack::init()->load_view( 'admin/my-jetpack-page.php' );
|
||||
|
||||
//My Jetpack view tracking, send to MC Stats
|
||||
Jetpack::init()->stat( 'admin', 'my-jetpack' );
|
||||
Jetpack::init()->do_stats( 'server_side' );
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle the change in master user
|
||||
*/
|
||||
function jetpack_my_jetpack_change_user() {
|
||||
if ( ! isset( $_POST['_my_jetpack_nonce'] ) || ! wp_verify_nonce( $_POST['_my_jetpack_nonce'], 'jetpack_change_primary_user' ) ) {
|
||||
wp_die( __( 'Failed permissions, please try again.', 'jetpack' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( isset( $_POST['jetpack-new-master'] ) ) {
|
||||
$old_master_user = Jetpack_Options::get_option( 'master_user' );
|
||||
$new_master_user = $_POST['jetpack-new-master'];
|
||||
$user_token = Jetpack_Data::get_access_token( $new_master_user );
|
||||
$is_user_connected = $user_token && ! is_wp_error( $user_token );
|
||||
if ( current_user_can( 'manage_options' ) && $is_user_connected ) {
|
||||
Jetpack::log( 'switch_master_user', array( 'old_master' => $old_master_user, 'new_master' => $new_master_user ) );
|
||||
Jetpack_Options::update_option( 'master_user', $new_master_user );
|
||||
Jetpack::state( 'message', 'switch_master' );
|
||||
|
||||
//My Jetpack primary user successfully changed, send to MC Stats
|
||||
Jetpack::init()->stat( 'admin', 'change-primary-successful' );
|
||||
Jetpack::init()->do_stats( 'server_side' );
|
||||
|
||||
// Change the blog owner dotcom side
|
||||
$this->wpcom_switch_blog_owner( $new_master_user );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Tell wpcom that the master user has switched
|
||||
* so we can update the 'wpcom_blog_owner'
|
||||
*/
|
||||
function wpcom_switch_blog_owner( $new_master ) {
|
||||
$request = array(
|
||||
'new_blog_owner' => $new_master
|
||||
);
|
||||
|
||||
// Tell wpcom about the change
|
||||
Jetpack::load_xml_rpc_client();
|
||||
$xml = new Jetpack_IXR_Client( array(
|
||||
'user_id' => get_current_user_id(),
|
||||
) );
|
||||
|
||||
$xml->query( 'jetpack.switchBlogOwner', $request );
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks to see if there are any other users available to become primary
|
||||
* Users must both:
|
||||
* - Be linked to wpcom
|
||||
* - Be an admin
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function jetpack_are_other_users_linked_and_admin() {
|
||||
// If only one admin
|
||||
$all_users = count_users();
|
||||
if ( 2 > $all_users['avail_roles']['administrator'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$users = get_users();
|
||||
$available = array();
|
||||
// If no one else is linked to dotcom
|
||||
foreach ( $users as $user ) {
|
||||
if ( isset( $user->caps['administrator'] ) && Jetpack::is_user_connected( $user->ID ) ) {
|
||||
$available[] = $user->ID;
|
||||
}
|
||||
}
|
||||
|
||||
if ( 2 > count( $available ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* All the data we'll need about the Master User
|
||||
* for the My Jetpack page template
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function jetpack_master_user_data() {
|
||||
// If the master user has disappeared, none of this is useful.
|
||||
// @todo throw up a warning and offer a solution
|
||||
$master = Jetpack_Options::get_option( 'master_user' );
|
||||
if ( ! get_user_by( 'id', $master ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$master_user = get_userdata( $master );
|
||||
$master_user_data_com = Jetpack::get_connected_user_data( $master_user->ID );
|
||||
$gravatar = sprintf( '<a href="%s">%s</a>', get_edit_user_link( $master_user->ID ), get_avatar( $master_user->ID, 40 ) );
|
||||
|
||||
$master_user_data = array(
|
||||
'masterUser' => $master_user,
|
||||
'masterDataCom' => $master_user_data_com,
|
||||
'gravatar' => $gravatar,
|
||||
);
|
||||
|
||||
return $master_user_data;
|
||||
}
|
||||
|
||||
/*
|
||||
* All the data we'll need about the Current User
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function jetpack_current_user_data() {
|
||||
global $current_user;
|
||||
$is_master_user = $current_user->ID == Jetpack_Options::get_option( 'master_user' );
|
||||
$dotcom_data = Jetpack::get_connected_user_data();
|
||||
|
||||
$current_user_data = array(
|
||||
'isUserConnected' => Jetpack::is_user_connected( $current_user->ID ),
|
||||
'isMasterUser' => $is_master_user,
|
||||
'adminUsername' => $current_user->user_login,
|
||||
'userComData' => $dotcom_data,
|
||||
'gravatar' => sprintf( '<a href="%s">%s</a>', get_edit_user_link( $current_user->ID ), get_avatar( $current_user->ID, 40 ) ),
|
||||
);
|
||||
|
||||
return $current_user_data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Build an array of My Jetpack stats urls.
|
||||
* requires the build URL args passed as an array
|
||||
*
|
||||
* @param array $my_jetpack_stats
|
||||
* @return (array) of built stats urls
|
||||
*/
|
||||
function build_my_jetpack_stats_urls( $my_jetpack_stats ) {
|
||||
$my_jetpack_urls = array();
|
||||
|
||||
foreach ( $my_jetpack_stats as $value ) {
|
||||
$my_jetpack_urls[ $value ] = Jetpack::build_stats_url( array( 'x_jetpack-admin' => $value ) );
|
||||
}
|
||||
|
||||
return $my_jetpack_urls;
|
||||
|
||||
}
|
||||
|
||||
// Load up admin scripts
|
||||
function page_admin_scripts() {
|
||||
wp_enqueue_script( 'jp-connection-js', plugins_url( '_inc/jp-my-jetpack.js', JETPACK__PLUGIN_FILE ), array( 'jquery', 'wp-util' ), JETPACK__VERSION . 'yep' );
|
||||
|
||||
wp_localize_script( 'jp-connection-js', 'jpConnection',
|
||||
array(
|
||||
'jetpackIsActive' => Jetpack::is_active(),
|
||||
'isAdmin' => current_user_can( 'jetpack_manage_modules' ),
|
||||
'otherAdminsLinked' => $this->jetpack_are_other_users_linked_and_admin(),
|
||||
'masterUser' => $this->jetpack_master_user_data(),
|
||||
'currentUser' => $this->jetpack_current_user_data(),
|
||||
'my_jetpack_stats_urls' => $this->build_my_jetpack_stats_urls( array( 'change_primary', 'disconnect_site', 'confirm_disconnect', 'support_no_disconnect', 'cancel_disconnect' ) ),
|
||||
'alertText' => __( 'You must link another admin account before switching primary account holders.', 'jetpack' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
include_once( 'class.jetpack-admin-page.php' );
|
||||
include_once( JETPACK__PLUGIN_DIR . 'class.jetpack-modules-list-table.php' );
|
||||
|
||||
// Builds the settings page and its menu
|
||||
class Jetpack_Settings_Page extends Jetpack_Admin_Page {
|
||||
// Show the settings page only when Jetpack is connected or in dev mode
|
||||
protected $dont_show_if_not_active = true;
|
||||
function add_page_actions( $hook ) {} // There are no page specific actions to attach to the menu
|
||||
|
||||
// Adds the Settings sub menu
|
||||
function get_page_hook() {
|
||||
return add_submenu_page( 'jetpack', __( 'Jetpack Settings', 'jetpack' ), __( 'Settings', 'jetpack' ), 'jetpack_manage_modules', 'jetpack_modules', array( $this, 'render' ) );
|
||||
}
|
||||
|
||||
// Renders the module list table where you can use bulk action or row
|
||||
// actions to activate/deactivate and configure modules
|
||||
function page_render() {
|
||||
$list_table = new Jetpack_Modules_List_Table;
|
||||
?>
|
||||
<div class="clouds-sm"></div>
|
||||
<?php /** This action is already documented in views/admin/admin-page.php */
|
||||
do_action( 'jetpack_notices' ) ?>
|
||||
<div class="page-content configure">
|
||||
<div class="frame top hide-if-no-js">
|
||||
<div class="wrap">
|
||||
<div class="manage-left">
|
||||
<table class="table table-bordered fixed-top">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="check-column"><input type="checkbox" class="checkall"></th>
|
||||
<th colspan="2">
|
||||
<?php $list_table->unprotected_display_tablenav( 'top' ); ?>
|
||||
<span class="filter-search">
|
||||
<button type="button" class="button">Filter</button>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div><!-- /.wrap -->
|
||||
</div><!-- /.frame -->
|
||||
<div class="frame bottom">
|
||||
<div class="wrap">
|
||||
<div class="manage-right">
|
||||
<div class="bumper">
|
||||
<form class="navbar-form" role="search">
|
||||
<input type="hidden" name="page" value="jetpack_modules" />
|
||||
<?php $list_table->search_box( __( 'Search', 'jetpack' ), 'srch-term' ); ?>
|
||||
<p><?php esc_html_e( 'View:', 'jetpack' ); ?></p>
|
||||
<div class="button-group filter-active">
|
||||
<button type="button" class="button <?php if ( empty( $_GET['activated'] ) ) echo 'active'; ?>"><?php esc_html_e( 'All', 'jetpack' ); ?></button>
|
||||
<button type="button" class="button <?php if ( ! empty( $_GET['activated'] ) && 'true' == $_GET['activated'] ) echo 'active'; ?>" data-filter-by="activated" data-filter-value="true"><?php esc_html_e( 'Active', 'jetpack' ); ?></button>
|
||||
<button type="button" class="button <?php if ( ! empty( $_GET['activated'] ) && 'false' == $_GET['activated'] ) echo 'active'; ?>" data-filter-by="activated" data-filter-value="false"><?php esc_html_e( 'Inactive', 'jetpack' ); ?></button>
|
||||
</div>
|
||||
<p><?php esc_html_e( 'Sort by:', 'jetpack' ); ?></p>
|
||||
<div class="button-group sort">
|
||||
<button type="button" class="button <?php if ( empty( $_GET['sort_by'] ) ) echo 'active'; ?>" data-sort-by="name"><?php esc_html_e( 'Alphabetical', 'jetpack' ); ?></button>
|
||||
<button type="button" class="button <?php if ( ! empty( $_GET['sort_by'] ) && 'introduced' == $_GET['sort_by'] ) echo 'active'; ?>" data-sort-by="introduced" data-sort-order="reverse"><?php esc_html_e( 'Newest', 'jetpack' ); ?></button>
|
||||
<button type="button" class="button <?php if ( ! empty( $_GET['sort_by'] ) && 'sort' == $_GET['sort_by'] ) echo 'active'; ?>" data-sort-by="sort"><?php esc_html_e( 'Popular', 'jetpack' ); ?></button>
|
||||
</div>
|
||||
<p><?php esc_html_e( 'Show:', 'jetpack' ); ?></p>
|
||||
<?php $list_table->views(); ?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manage-left">
|
||||
<form class="jetpack-modules-list-table-form" onsubmit="return false;">
|
||||
<table class="<?php echo implode( ' ', $list_table->get_table_classes() ); ?>">
|
||||
<tbody id="the-list">
|
||||
<?php $list_table->display_rows_or_placeholder(); ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
</div><!-- /.wrap -->
|
||||
</div><!-- /.frame -->
|
||||
</div><!-- /.content -->
|
||||
<?php
|
||||
}
|
||||
|
||||
// Javascript logic specific to the list table
|
||||
function page_admin_scripts() {
|
||||
wp_enqueue_script( 'jetpack-admin-js', plugins_url( '_inc/jetpack-admin.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), JETPACK__VERSION . '-20121111' );
|
||||
}
|
||||
}
|
755
plugins/jetpack/_inc/lib/class.color.php
Normal file
755
plugins/jetpack/_inc/lib/class.color.php
Normal file
|
@ -0,0 +1,755 @@
|
|||
<?php
|
||||
/**
|
||||
* Color utility and conversion
|
||||
*
|
||||
* Represents a color value, and converts between RGB/HSV/XYZ/Lab/HSL
|
||||
*
|
||||
* Example:
|
||||
* $color = new Jetpack_Color(0xFFFFFF);
|
||||
*
|
||||
* @author Harold Asbridge <hasbridge@gmail.com>
|
||||
* @author Matt Wiebe <wiebe@automattic.com>
|
||||
* @license http://www.opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
class Jetpack_Color {
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $color = 0;
|
||||
|
||||
/**
|
||||
* Initialize object
|
||||
*
|
||||
* @param string|array $color A color of the type $type
|
||||
* @param string $type The type of color we will construct from.
|
||||
* One of hex (default), rgb, hsl, int
|
||||
*/
|
||||
public function __construct( $color = null, $type = 'hex' ) {
|
||||
if ( $color ) {
|
||||
switch ( $type ) {
|
||||
case 'hex':
|
||||
$this->fromHex( $color );
|
||||
break;
|
||||
case 'rgb':
|
||||
if ( is_array( $color ) && count( $color ) == 3 ) {
|
||||
list( $r, $g, $b ) = array_values( $color );
|
||||
$this->fromRgbInt( $r, $g, $b );
|
||||
}
|
||||
break;
|
||||
case 'hsl':
|
||||
if ( is_array( $color ) && count( $color ) == 3 ) {
|
||||
list( $h, $s, $l ) = array_values( $color );
|
||||
$this->fromHsl( $h, $s, $l );
|
||||
}
|
||||
break;
|
||||
case 'int':
|
||||
$this->fromInt( $color );
|
||||
break;
|
||||
default:
|
||||
// there is no default.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init color from hex value
|
||||
*
|
||||
* @param string $hexValue
|
||||
*
|
||||
* @return Jetpack_Color
|
||||
*/
|
||||
public function fromHex($hexValue) {
|
||||
$hexValue = str_replace( '#', '', $hexValue );
|
||||
// handle short hex codes like #fff
|
||||
if ( 3 === strlen( $hexValue ) ) {
|
||||
$short = $hexValue;
|
||||
$i = 0;
|
||||
$hexValue = '';
|
||||
while ( $i < 3 ) {
|
||||
$chunk = substr($short, $i, 1 );
|
||||
$hexValue .= $chunk . $chunk;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
$intValue = hexdec( $hexValue );
|
||||
|
||||
if ( $intValue < 0 || $intValue > 16777215 ) {
|
||||
throw new RangeException( $hexValue . " out of valid color code range" );
|
||||
}
|
||||
|
||||
$this->color = $intValue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init color from integer RGB values
|
||||
*
|
||||
* @param int $red
|
||||
* @param int $green
|
||||
* @param int $blue
|
||||
*
|
||||
* @return Jetpack_Color
|
||||
*/
|
||||
public function fromRgbInt($red, $green, $blue)
|
||||
{
|
||||
if ( $red < 0 || $red > 255 )
|
||||
throw new RangeException( "Red value " . $red . " out of valid color code range" );
|
||||
|
||||
if ( $green < 0 || $green > 255 )
|
||||
throw new RangeException( "Green value " . $green . " out of valid color code range" );
|
||||
|
||||
if ( $blue < 0 || $blue > 255 )
|
||||
throw new RangeException( "Blue value " . $blue . " out of valid color code range" );
|
||||
|
||||
$this->color = (int)(($red << 16) + ($green << 8) + $blue);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init color from hex RGB values
|
||||
*
|
||||
* @param string $red
|
||||
* @param string $green
|
||||
* @param string $blue
|
||||
*
|
||||
* @return Jetpack_Color
|
||||
*/
|
||||
public function fromRgbHex($red, $green, $blue)
|
||||
{
|
||||
return $this->fromRgbInt(hexdec($red), hexdec($green), hexdec($blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HSL color value to RGB. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* @param int $h Hue. [0-360]
|
||||
* @param in $s Saturation [0, 100]
|
||||
* @param int $l Lightness [0, 100]
|
||||
*/
|
||||
public function fromHsl( $h, $s, $l ) {
|
||||
$h /= 360; $s /= 100; $l /= 100;
|
||||
|
||||
if ( $s == 0 ) {
|
||||
$r = $g = $b = $l; // achromatic
|
||||
}
|
||||
else {
|
||||
$q = $l < 0.5 ? $l * ( 1 + $s ) : $l + $s - $l * $s;
|
||||
$p = 2 * $l - $q;
|
||||
$r = $this->hue2rgb( $p, $q, $h + 1/3 );
|
||||
$g = $this->hue2rgb( $p, $q, $h );
|
||||
$b = $this->hue2rgb( $p, $q, $h - 1/3 );
|
||||
}
|
||||
|
||||
return $this->fromRgbInt( $r * 255, $g * 255, $b * 255 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for Jetpack_Color::fromHsl()
|
||||
*/
|
||||
private function hue2rgb( $p, $q, $t ) {
|
||||
if ( $t < 0 ) $t += 1;
|
||||
if ( $t > 1 ) $t -= 1;
|
||||
if ( $t < 1/6 ) return $p + ( $q - $p ) * 6 * $t;
|
||||
if ( $t < 1/2 ) return $q;
|
||||
if ( $t < 2/3 ) return $p + ( $q - $p ) * ( 2/3 - $t ) * 6;
|
||||
return $p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init color from integer value
|
||||
*
|
||||
* @param int $intValue
|
||||
*
|
||||
* @return Jetpack_Color
|
||||
*/
|
||||
public function fromInt($intValue)
|
||||
{
|
||||
if ( $intValue < 0 || $intValue > 16777215 )
|
||||
throw new RangeException( $intValue . " out of valid color code range" );
|
||||
|
||||
$this->color = $intValue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert color to hex
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHex()
|
||||
{
|
||||
return str_pad(dechex($this->color), 6, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert color to RGB array (integer values)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toRgbInt()
|
||||
{
|
||||
return array(
|
||||
'red' => (int)(255 & ($this->color >> 16)),
|
||||
'green' => (int)(255 & ($this->color >> 8)),
|
||||
'blue' => (int)(255 & ($this->color))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert color to RGB array (hex values)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toRgbHex()
|
||||
{
|
||||
$r = array();
|
||||
foreach ($this->toRgbInt() as $item) {
|
||||
$r[] = dechex($item);
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Hue/Saturation/Value for the current color
|
||||
* (float values, slow but accurate)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toHsvFloat()
|
||||
{
|
||||
$rgb = $this->toRgbInt();
|
||||
|
||||
$rgbMin = min($rgb);
|
||||
$rgbMax = max($rgb);
|
||||
|
||||
$hsv = array(
|
||||
'hue' => 0,
|
||||
'sat' => 0,
|
||||
'val' => $rgbMax
|
||||
);
|
||||
|
||||
// If v is 0, color is black
|
||||
if ($hsv['val'] == 0) {
|
||||
return $hsv;
|
||||
}
|
||||
|
||||
// Normalize RGB values to 1
|
||||
$rgb['red'] /= $hsv['val'];
|
||||
$rgb['green'] /= $hsv['val'];
|
||||
$rgb['blue'] /= $hsv['val'];
|
||||
$rgbMin = min($rgb);
|
||||
$rgbMax = max($rgb);
|
||||
|
||||
// Calculate saturation
|
||||
$hsv['sat'] = $rgbMax - $rgbMin;
|
||||
if ($hsv['sat'] == 0) {
|
||||
$hsv['hue'] = 0;
|
||||
return $hsv;
|
||||
}
|
||||
|
||||
// Normalize saturation to 1
|
||||
$rgb['red'] = ($rgb['red'] - $rgbMin) / ($rgbMax - $rgbMin);
|
||||
$rgb['green'] = ($rgb['green'] - $rgbMin) / ($rgbMax - $rgbMin);
|
||||
$rgb['blue'] = ($rgb['blue'] - $rgbMin) / ($rgbMax - $rgbMin);
|
||||
$rgbMin = min($rgb);
|
||||
$rgbMax = max($rgb);
|
||||
|
||||
// Calculate hue
|
||||
if ($rgbMax == $rgb['red']) {
|
||||
$hsv['hue'] = 0.0 + 60 * ($rgb['green'] - $rgb['blue']);
|
||||
if ($hsv['hue'] < 0) {
|
||||
$hsv['hue'] += 360;
|
||||
}
|
||||
} else if ($rgbMax == $rgb['green']) {
|
||||
$hsv['hue'] = 120 + (60 * ($rgb['blue'] - $rgb['red']));
|
||||
} else {
|
||||
$hsv['hue'] = 240 + (60 * ($rgb['red'] - $rgb['green']));
|
||||
}
|
||||
|
||||
return $hsv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HSV values for color
|
||||
* (integer values from 0-255, fast but less accurate)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function toHsvInt()
|
||||
{
|
||||
$rgb = $this->toRgbInt();
|
||||
|
||||
$rgbMin = min($rgb);
|
||||
$rgbMax = max($rgb);
|
||||
|
||||
$hsv = array(
|
||||
'hue' => 0,
|
||||
'sat' => 0,
|
||||
'val' => $rgbMax
|
||||
);
|
||||
|
||||
// If value is 0, color is black
|
||||
if ($hsv['val'] == 0) {
|
||||
return $hsv;
|
||||
}
|
||||
|
||||
// Calculate saturation
|
||||
$hsv['sat'] = round(255 * ($rgbMax - $rgbMin) / $hsv['val']);
|
||||
if ($hsv['sat'] == 0) {
|
||||
$hsv['hue'] = 0;
|
||||
return $hsv;
|
||||
}
|
||||
|
||||
// Calculate hue
|
||||
if ($rgbMax == $rgb['red']) {
|
||||
$hsv['hue'] = round(0 + 43 * ($rgb['green'] - $rgb['blue']) / ($rgbMax - $rgbMin));
|
||||
} else if ($rgbMax == $rgb['green']) {
|
||||
$hsv['hue'] = round(85 + 43 * ($rgb['blue'] - $rgb['red']) / ($rgbMax - $rgbMin));
|
||||
} else {
|
||||
$hsv['hue'] = round(171 + 43 * ($rgb['red'] - $rgb['green']) / ($rgbMax - $rgbMin));
|
||||
}
|
||||
if ($hsv['hue'] < 0) {
|
||||
$hsv['hue'] += 255;
|
||||
}
|
||||
|
||||
return $hsv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an RGB color value to HSL. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes r, g, and b are contained in the set [0, 255] and
|
||||
* returns h in [0, 360], s in [0, 100], l in [0, 100]
|
||||
*
|
||||
* @return Array The HSL representation
|
||||
*/
|
||||
public function toHsl() {
|
||||
list( $r, $g, $b ) = array_values( $this->toRgbInt() );
|
||||
$r /= 255; $g /= 255; $b /= 255;
|
||||
$max = max( $r, $g, $b );
|
||||
$min = min( $r, $g, $b );
|
||||
$h = $s = $l = ( $max + $min ) / 2;
|
||||
#var_dump( array( compact('max', 'min', 'r', 'g', 'b')) );
|
||||
if ( $max == $min ) {
|
||||
$h = $s = 0; // achromatic
|
||||
}
|
||||
else {
|
||||
$d = $max - $min;
|
||||
$s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min );
|
||||
switch ( $max ) {
|
||||
case $r:
|
||||
$h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
|
||||
break;
|
||||
case $g:
|
||||
$h = ( $b - $r ) / $d + 2;
|
||||
break;
|
||||
case $b:
|
||||
$h = ( $r - $g ) / $d + 4;
|
||||
break;
|
||||
}
|
||||
$h /= 6;
|
||||
}
|
||||
$h = (int) round( $h * 360 );
|
||||
$s = (int) round( $s * 100 );
|
||||
$l = (int) round( $l * 100 );
|
||||
return compact( 'h', 's', 'l' );
|
||||
}
|
||||
|
||||
public function toCSS( $type = 'hex', $alpha = 1 ) {
|
||||
switch ( $type ) {
|
||||
case 'hex':
|
||||
return $this->toString();
|
||||
break;
|
||||
case 'rgb':
|
||||
case 'rgba':
|
||||
list( $r, $g, $b ) = array_values( $this->toRgbInt() );
|
||||
if ( is_numeric( $alpha ) && $alpha < 1 ) {
|
||||
return "rgba( {$r}, {$g}, {$b}, $alpha )";
|
||||
}
|
||||
else {
|
||||
return "rgb( {$r}, {$g}, {$b} )";
|
||||
}
|
||||
break;
|
||||
case 'hsl':
|
||||
case 'hsla':
|
||||
list( $h, $s, $l ) = array_values( $this->toHsl() );
|
||||
if ( is_numeric( $alpha ) && $alpha < 1 ) {
|
||||
return "hsla( {$h}, {$s}, {$l}, $alpha )";
|
||||
}
|
||||
else {
|
||||
return "hsl( {$h}, {$s}, {$l} )";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return $this->toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current color in XYZ format
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toXyz()
|
||||
{
|
||||
$rgb = $this->toRgbInt();
|
||||
|
||||
// Normalize RGB values to 1
|
||||
|
||||
$rgb_new = array();
|
||||
foreach ($rgb as $item) {
|
||||
$rgb_new[] = $item / 255;
|
||||
}
|
||||
$rgb = $rgb_new;
|
||||
|
||||
$rgb_new = array();
|
||||
foreach ($rgb as $item) {
|
||||
if ($item > 0.04045) {
|
||||
$item = pow((($item + 0.055) / 1.055), 2.4);
|
||||
} else {
|
||||
$item = $item / 12.92;
|
||||
}
|
||||
$rgb_new[] = $item * 100;
|
||||
}
|
||||
$rgb = $rgb_new;
|
||||
|
||||
// Observer. = 2°, Illuminant = D65
|
||||
$xyz = array(
|
||||
'x' => ($rgb['red'] * 0.4124) + ($rgb['green'] * 0.3576) + ($rgb['blue'] * 0.1805),
|
||||
'y' => ($rgb['red'] * 0.2126) + ($rgb['green'] * 0.7152) + ($rgb['blue'] * 0.0722),
|
||||
'z' => ($rgb['red'] * 0.0193) + ($rgb['green'] * 0.1192) + ($rgb['blue'] * 0.9505)
|
||||
);
|
||||
|
||||
return $xyz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color CIE-Lab values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toLabCie()
|
||||
{
|
||||
$xyz = $this->toXyz();
|
||||
|
||||
//Ovserver = 2*, Iluminant=D65
|
||||
$xyz['x'] /= 95.047;
|
||||
$xyz['y'] /= 100;
|
||||
$xyz['z'] /= 108.883;
|
||||
|
||||
$xyz_new = array();
|
||||
foreach ($xyz as $item) {
|
||||
if ($item > 0.008856) {
|
||||
$xyz_new[] = pow($item, 1/3);
|
||||
} else {
|
||||
$xyz_new[] = (7.787 * $item) + (16 / 116);
|
||||
}
|
||||
}
|
||||
$xyz = $xyz_new;
|
||||
|
||||
$lab = array(
|
||||
'l' => (116 * $xyz['y']) - 16,
|
||||
'a' => 500 * ($xyz['x'] - $xyz['y']),
|
||||
'b' => 200 * ($xyz['y'] - $xyz['z'])
|
||||
);
|
||||
|
||||
return $lab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert color to integer
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function toInt()
|
||||
{
|
||||
return $this->color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of toString()
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color as string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toString()
|
||||
{
|
||||
$str = $this->toHex();
|
||||
return strtoupper("#{$str}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distance between this color and the given color
|
||||
*
|
||||
* @param Jetpack_Color $color
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDistanceRgbFrom(Jetpack_Color $color)
|
||||
{
|
||||
$rgb1 = $this->toRgbInt();
|
||||
$rgb2 = $color->toRgbInt();
|
||||
|
||||
$rDiff = abs($rgb1['red'] - $rgb2['red']);
|
||||
$gDiff = abs($rgb1['green'] - $rgb2['green']);
|
||||
$bDiff = abs($rgb1['blue'] - $rgb2['blue']);
|
||||
|
||||
// Sum of RGB differences
|
||||
$diff = $rDiff + $gDiff + $bDiff;
|
||||
return $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get distance from the given color using the Delta E method
|
||||
*
|
||||
* @param Jetpack_Color $color
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getDistanceLabFrom(Jetpack_Color $color)
|
||||
{
|
||||
$lab1 = $this->toLabCie();
|
||||
$lab2 = $color->toLabCie();
|
||||
|
||||
$lDiff = abs($lab2['l'] - $lab1['l']);
|
||||
$aDiff = abs($lab2['a'] - $lab1['a']);
|
||||
$bDiff = abs($lab2['b'] - $lab1['b']);
|
||||
|
||||
$delta = sqrt($lDiff + $aDiff + $bDiff);
|
||||
|
||||
return $delta;
|
||||
}
|
||||
|
||||
public function toLuminosity() {
|
||||
$lum = array();
|
||||
foreach( $this->toRgbInt() as $slot => $value ) {
|
||||
$chan = $value / 255;
|
||||
$lum[ $slot ] = ( $chan <= 0.03928 ) ? $chan / 12.92 : pow( ( ( $chan + 0.055 ) / 1.055 ), 2.4 );
|
||||
}
|
||||
return 0.2126 * $lum['red'] + 0.7152 * $lum['green'] + 0.0722 * $lum['blue'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get distance between colors using luminance.
|
||||
* Should be more than 5 for readable contrast
|
||||
*
|
||||
* @param Jetpack_Color $color Another color
|
||||
* @return float
|
||||
*/
|
||||
public function getDistanceLuminosityFrom( Jetpack_Color $color ) {
|
||||
$L1 = $this->toLuminosity();
|
||||
$L2 = $color->toLuminosity();
|
||||
if ( $L1 > $L2 ) {
|
||||
return ( $L1 + 0.05 ) / ( $L2 + 0.05 );
|
||||
}
|
||||
else{
|
||||
return ( $L2 + 0.05 ) / ( $L1 + 0.05 );
|
||||
}
|
||||
}
|
||||
|
||||
public function getMaxContrastColor() {
|
||||
$withBlack = $this->getDistanceLuminosityFrom( new Jetpack_Color( '#000') );
|
||||
$withWhite = $this->getDistanceLuminosityFrom( new Jetpack_Color( '#fff') );
|
||||
$color = new Jetpack_Color;
|
||||
$hex = ( $withBlack >= $withWhite ) ? '#000000' : '#ffffff';
|
||||
return $color->fromHex( $hex );
|
||||
}
|
||||
|
||||
public function getGrayscaleContrastingColor( $contrast = false ) {
|
||||
if ( ! $contrast ) {
|
||||
return $this->getMaxContrastColor();
|
||||
}
|
||||
// don't allow less than 5
|
||||
$target_contrast = ( $contrast < 5 ) ? 5 : $contrast;
|
||||
$color = $this->getMaxContrastColor();
|
||||
$contrast = $color->getDistanceLuminosityFrom( $this );
|
||||
|
||||
// if current max contrast is less than the target contrast, we had wishful thinking.
|
||||
if ( $contrast <= $target_contrast ) {
|
||||
return $color;
|
||||
}
|
||||
|
||||
$incr = ( '#000000' === $color->toString() ) ? 1 : -1;
|
||||
while ( $contrast > $target_contrast ) {
|
||||
$color = $color->incrementLightness( $incr );
|
||||
$contrast = $color->getDistanceLuminosityFrom( $this );
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a readable contrasting color. $this is assumed to be the text and $color the background color.
|
||||
* @param object $bg_color A Color object that will be compared against $this
|
||||
* @param integer $min_contrast The minimum contrast to achieve, if possible.
|
||||
* @return object A Color object, an increased contrast $this compared against $bg_color
|
||||
*/
|
||||
public function getReadableContrastingColor( $bg_color = false, $min_contrast = 5 ) {
|
||||
if ( ! $bg_color || ! is_a( $bg_color, 'Jetpack_Color' ) ) {
|
||||
return $this;
|
||||
}
|
||||
// you shouldn't use less than 5, but you might want to.
|
||||
$target_contrast = $min_contrast;
|
||||
// working things
|
||||
$contrast = $bg_color->getDistanceLuminosityFrom( $this );
|
||||
$max_contrast_color = $bg_color->getMaxContrastColor();
|
||||
$max_contrast = $max_contrast_color->getDistanceLuminosityFrom( $bg_color );
|
||||
|
||||
// if current max contrast is less than the target contrast, we had wishful thinking.
|
||||
// still, go max
|
||||
if ( $max_contrast <= $target_contrast ) {
|
||||
return $max_contrast_color;
|
||||
}
|
||||
// or, we might already have sufficient contrast
|
||||
if ( $contrast >= $target_contrast ) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$incr = ( 0 === $max_contrast_color->toInt() ) ? -1 : 1;
|
||||
while ( $contrast < $target_contrast ) {
|
||||
$this->incrementLightness( $incr );
|
||||
$contrast = $bg_color->getDistanceLuminosityFrom( $this );
|
||||
// infininite loop prevention: you never know.
|
||||
if ( $this->color === 0 || $this->color === 16777215 ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if color is grayscale
|
||||
*
|
||||
* @param int @threshold
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isGrayscale($threshold = 16)
|
||||
{
|
||||
$rgb = $this->toRgbInt();
|
||||
|
||||
// Get min and max rgb values, then difference between them
|
||||
$rgbMin = min($rgb);
|
||||
$rgbMax = max($rgb);
|
||||
$diff = $rgbMax - $rgbMin;
|
||||
|
||||
return $diff < $threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the closest matching color from the given array of colors
|
||||
*
|
||||
* @param array $colors array of integers or Jetpack_Color objects
|
||||
*
|
||||
* @return mixed the array key of the matched color
|
||||
*/
|
||||
public function getClosestMatch(array $colors)
|
||||
{
|
||||
$matchDist = 10000;
|
||||
$matchKey = null;
|
||||
foreach($colors as $key => $color) {
|
||||
if (false === ($color instanceof Jetpack_Color)) {
|
||||
$c = new Jetpack_Color($color);
|
||||
}
|
||||
$dist = $this->getDistanceLabFrom($c);
|
||||
if ($dist < $matchDist) {
|
||||
$matchDist = $dist;
|
||||
$matchKey = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return $matchKey;
|
||||
}
|
||||
|
||||
/* TRANSFORMS */
|
||||
|
||||
public function darken( $amount = 5 ) {
|
||||
return $this->incrementLightness( - $amount );
|
||||
}
|
||||
|
||||
public function lighten( $amount = 5 ) {
|
||||
return $this->incrementLightness( $amount );
|
||||
}
|
||||
|
||||
public function incrementLightness( $amount ) {
|
||||
$hsl = $this->toHsl();
|
||||
extract( $hsl );
|
||||
$l += $amount;
|
||||
if ( $l < 0 ) $l = 0;
|
||||
if ( $l > 100 ) $l = 100;
|
||||
return $this->fromHsl( $h, $s, $l );
|
||||
}
|
||||
|
||||
public function saturate( $amount = 15 ) {
|
||||
return $this->incrementSaturation( $amount );
|
||||
}
|
||||
|
||||
public function desaturate( $amount = 15 ) {
|
||||
return $this->incrementSaturation( - $amount );
|
||||
}
|
||||
|
||||
public function incrementSaturation( $amount ) {
|
||||
$hsl = $this->toHsl();
|
||||
extract( $hsl );
|
||||
$s += $amount;
|
||||
if ( $s < 0 ) $s = 0;
|
||||
if ( $s > 100 ) $s = 100;
|
||||
return $this->fromHsl( $h, $s, $l );
|
||||
}
|
||||
|
||||
public function toGrayscale() {
|
||||
$hsl = $this->toHsl();
|
||||
extract( $hsl );
|
||||
$s = 0;
|
||||
return $this->fromHsl( $h, $s, $l );
|
||||
}
|
||||
|
||||
public function getComplement() {
|
||||
return $this->incrementHue( 180 );
|
||||
}
|
||||
|
||||
public function getSplitComplement( $step = 1 ) {
|
||||
$incr = 180 + ( $step * 30 );
|
||||
return $this->incrementHue( $incr );
|
||||
}
|
||||
|
||||
public function getAnalog( $step = 1 ) {
|
||||
$incr = $step * 30;
|
||||
return $this->incrementHue( $incr );
|
||||
}
|
||||
|
||||
public function getTetrad( $step = 1 ) {
|
||||
$incr = $step * 60;
|
||||
return $this->incrementHue( $incr );
|
||||
}
|
||||
|
||||
public function getTriad( $step = 1 ) {
|
||||
$incr = $step * 120;
|
||||
return $this->incrementHue( $incr );
|
||||
}
|
||||
|
||||
public function incrementHue( $amount ) {
|
||||
$hsl = $this->toHsl();
|
||||
extract( $hsl );
|
||||
$h = ( $h + $amount ) % 360;
|
||||
if ( $h < 0 ) $h = 360 - $h;
|
||||
return $this->fromHsl( $h, $s, $l );
|
||||
}
|
||||
|
||||
} // class Jetpack_Color
|
436
plugins/jetpack/_inc/lib/class.media-extractor.php
Normal file
436
plugins/jetpack/_inc/lib/class.media-extractor.php
Normal file
|
@ -0,0 +1,436 @@
|
|||
<?php
|
||||
/**
|
||||
* Class with methods to extract metadata from a post/page about videos, images, links, mentions embedded
|
||||
* in or attached to the post/page.
|
||||
*
|
||||
* @todo Additionally, have some filters on number of items in each field
|
||||
*/
|
||||
class Jetpack_Media_Meta_Extractor {
|
||||
|
||||
// Some consts for what to extract
|
||||
const ALL = 255;
|
||||
const LINKS = 1;
|
||||
const MENTIONS = 2;
|
||||
const IMAGES = 4;
|
||||
const SHORTCODES = 8; // Only the keeper shortcodes below
|
||||
const EMBEDS = 16;
|
||||
const HASHTAGS = 32;
|
||||
|
||||
// For these, we try to extract some data from the shortcode, rather than just recording its presence (which we do for all)
|
||||
// There should be a function get_{shortcode}_id( $atts ) or static method SomethingShortcode::get_{shortcode}_id( $atts ) for these.
|
||||
private static $KEEPER_SHORTCODES = array(
|
||||
'youtube',
|
||||
'vimeo',
|
||||
'hulu',
|
||||
'ted',
|
||||
'wpvideo',
|
||||
'audio',
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the specified media and meta info from the given post.
|
||||
* NOTE: If you have the post's HTML content already and don't need image data, use extract_from_content() instead.
|
||||
*
|
||||
* @param $blog_id The ID of the blog
|
||||
* @param $post_id The ID of the post
|
||||
* @param $what_to_extract (int) A mask of things to extract, e.g. Jetpack_Media_Meta_Extractor::IMAGES | Jetpack_Media_Meta_Extractor::MENTIONS
|
||||
* @returns a structure containing metadata about the embedded things, or empty array if nothing found, or WP_Error on error
|
||||
*/
|
||||
static public function extract( $blog_id, $post_id, $what_to_extract = self::ALL ) {
|
||||
|
||||
// multisite?
|
||||
if ( function_exists( 'switch_to_blog') )
|
||||
switch_to_blog( $blog_id );
|
||||
|
||||
$post = get_post( $post_id );
|
||||
$content = $post->post_title . "\n\n" . $post->post_content;
|
||||
$char_cnt = strlen( $content );
|
||||
|
||||
//prevent running extraction on really huge amounts of content
|
||||
if ( $char_cnt > 100000 ) //about 20k English words
|
||||
$content = substr( $content, 0, 100000 );
|
||||
|
||||
$extracted = array();
|
||||
|
||||
// Get images first, we need the full post for that
|
||||
if ( self::IMAGES & $what_to_extract ) {
|
||||
$extracted = self::get_image_fields( $post );
|
||||
|
||||
// Turn off images so we can safely call extract_from_content() below
|
||||
$what_to_extract = $what_to_extract - self::IMAGES;
|
||||
}
|
||||
|
||||
if ( function_exists( 'switch_to_blog') )
|
||||
restore_current_blog();
|
||||
|
||||
// All of the other things besides images can be extracted from just the content
|
||||
$extracted = self::extract_from_content( $content, $what_to_extract, $extracted );
|
||||
|
||||
return $extracted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the specified meta info from the given post content.
|
||||
* NOTE: If you want IMAGES, call extract( $blog_id, $post_id, ...) which will give you more/better image extraction
|
||||
* This method will give you an error if you ask for IMAGES.
|
||||
*
|
||||
* @param $content The HTML post_content of a post
|
||||
* @param $what_to_extract (int) A mask of things to extract, e.g. Jetpack_Media_Meta_Extractor::IMAGES | Jetpack_Media_Meta_Extractor::MENTIONS
|
||||
* @param $already_extracted (array) Previously extracted things, e.g. images from extract(), which can be used for x-referencing here
|
||||
* @returns a structure containing metadata about the embedded things, or empty array if nothing found, or WP_Error on error
|
||||
*/
|
||||
static public function extract_from_content( $content, $what_to_extract = self::ALL, $already_extracted = array() ) {
|
||||
$stripped_content = self::get_stripped_content( $content );
|
||||
|
||||
// Maybe start with some previously extracted things (e.g. images from extract()
|
||||
$extracted = $already_extracted;
|
||||
|
||||
// Embedded media objects will have already been converted to shortcodes by pre_kses hooks on save.
|
||||
|
||||
if ( self::IMAGES & $what_to_extract ) {
|
||||
$images = Jetpack_Media_Meta_Extractor::extract_images_from_content( $stripped_content, array() );
|
||||
$extracted = array_merge( $extracted, $images );
|
||||
}
|
||||
|
||||
// ----------------------------------- MENTIONS ------------------------------
|
||||
|
||||
if ( self::MENTIONS & $what_to_extract ) {
|
||||
if ( preg_match_all( '/(^|\s)@(\w+)/u', $stripped_content, $matches ) ) {
|
||||
$mentions = array_values( array_unique( $matches[2] ) ); //array_unique() retains the keys!
|
||||
$mentions = array_map( 'strtolower', $mentions );
|
||||
$extracted['mention'] = array( 'name' => $mentions );
|
||||
if ( !isset( $extracted['has'] ) )
|
||||
$extracted['has'] = array();
|
||||
$extracted['has']['mention'] = count( $mentions );
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------- HASHTAGS ------------------------------
|
||||
/** Some hosts may not compile with --enable-unicode-properties and kick a warning:
|
||||
* Warning: preg_match_all() [function.preg-match-all]: Compilation failed: support for \P, \p, and \X has not been compiled
|
||||
* Therefore, we only run this code block on wpcom, not in Jetpack.
|
||||
*/
|
||||
if ( ( defined( 'IS_WPCOM' ) && IS_WPCOM ) && ( self::HASHTAGS & $what_to_extract ) ) {
|
||||
//This regex does not exactly match Twitter's
|
||||
// if there are problems/complaints we should implement this:
|
||||
// https://github.com/twitter/twitter-text/blob/master/java/src/com/twitter/Regex.java
|
||||
if ( preg_match_all( '/(?:^|\s)#(\w*\p{L}+\w*)/u', $stripped_content, $matches ) ) {
|
||||
$hashtags = array_values( array_unique( $matches[1] ) ); //array_unique() retains the keys!
|
||||
$hashtags = array_map( 'strtolower', $hashtags );
|
||||
$extracted['hashtag'] = array( 'name' => $hashtags );
|
||||
if ( !isset( $extracted['has'] ) )
|
||||
$extracted['has'] = array();
|
||||
$extracted['has']['hashtag'] = count( $hashtags );
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------- SHORTCODES ------------------------------
|
||||
|
||||
// Always look for shortcodes.
|
||||
// If we don't want them, we'll just remove them, so we don't grab them as links below
|
||||
$shortcode_pattern = '/' . get_shortcode_regex() . '/s';
|
||||
if ( preg_match_all( $shortcode_pattern, $content, $matches ) ) {
|
||||
|
||||
$shortcode_total_count = 0;
|
||||
$shortcode_type_counts = array();
|
||||
$shortcode_types = array();
|
||||
$shortcode_details = array();
|
||||
|
||||
if ( self::SHORTCODES & $what_to_extract ) {
|
||||
|
||||
foreach( $matches[2] as $key => $shortcode ) {
|
||||
//Elasticsearch (and probably other things) doesn't deal well with some chars as key names
|
||||
$shortcode_name = preg_replace( '/[.,*"\'\/\\\\#+ ]/', '_', $shortcode );
|
||||
|
||||
$attr = shortcode_parse_atts( $matches[3][ $key ] );
|
||||
|
||||
$shortcode_total_count++;
|
||||
if ( ! isset( $shortcode_type_counts[$shortcode_name] ) )
|
||||
$shortcode_type_counts[$shortcode_name] = 0;
|
||||
$shortcode_type_counts[$shortcode_name]++;
|
||||
|
||||
// Store (uniquely) presence of all shortcode regardless of whether it's a keeper (for those, get ID below)
|
||||
// @todo Store number of occurrences?
|
||||
if ( ! in_array( $shortcode_name, $shortcode_types ) )
|
||||
$shortcode_types[] = $shortcode_name;
|
||||
|
||||
// For keeper shortcodes, also store the id/url of the object (e.g. youtube video, TED talk, etc.)
|
||||
if ( in_array( $shortcode, self::$KEEPER_SHORTCODES ) ) {
|
||||
unset( $id ); // Clear shortcode ID data left from the last shortcode
|
||||
// We'll try to get the salient ID from the function jetpack_shortcode_get_xyz_id()
|
||||
// If the shortcode is a class, we'll call XyzShortcode::get_xyz_id()
|
||||
$shortcode_get_id_func = "jetpack_shortcode_get_{$shortcode}_id";
|
||||
$shortcode_class_name = ucfirst( $shortcode ) . 'Shortcode';
|
||||
$shortcode_get_id_method = "get_{$shortcode}_id";
|
||||
if ( function_exists( $shortcode_get_id_func ) ) {
|
||||
$id = call_user_func( $shortcode_get_id_func, $attr );
|
||||
} else if ( method_exists( $shortcode_class_name, $shortcode_get_id_method ) ) {
|
||||
$id = call_user_func( array( $shortcode_class_name, $shortcode_get_id_method ), $attr );
|
||||
}
|
||||
if ( ! empty( $id )
|
||||
&& ( ! isset( $shortcode_details[$shortcode_name] ) || ! in_array( $id, $shortcode_details[$shortcode_name] ) ) )
|
||||
$shortcode_details[$shortcode_name][] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $shortcode_total_count > 0 ) {
|
||||
// Add the shortcode info to the $extracted array
|
||||
if ( !isset( $extracted['has'] ) )
|
||||
$extracted['has'] = array();
|
||||
$extracted['has']['shortcode'] = $shortcode_total_count;
|
||||
$extracted['shortcode'] = array();
|
||||
foreach ( $shortcode_type_counts as $type => $count )
|
||||
$extracted['shortcode'][$type] = array( 'count' => $count );
|
||||
if ( ! empty( $shortcode_types ) )
|
||||
$extracted['shortcode_types'] = $shortcode_types;
|
||||
foreach ( $shortcode_details as $type => $id )
|
||||
$extracted['shortcode'][$type]['id'] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the shortcodes form our copy of $content, so we don't count links in them as links below.
|
||||
$content = preg_replace( $shortcode_pattern, ' ', $content );
|
||||
}
|
||||
|
||||
// ----------------------------------- LINKS ------------------------------
|
||||
|
||||
if ( self::LINKS & $what_to_extract ) {
|
||||
|
||||
// To hold the extracted stuff we find
|
||||
$links = array();
|
||||
|
||||
// @todo Get the text inside the links?
|
||||
|
||||
// Grab any links, whether in <a href="..." or not, but subtract those from shortcodes and images
|
||||
// (we treat embed links as just another link)
|
||||
if ( preg_match_all( '#(?:^|\s|"|\')(https?://([^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/))))#', $content, $matches ) ) {
|
||||
|
||||
foreach ( $matches[1] as $link_raw ) {
|
||||
$url = parse_url( $link_raw );
|
||||
|
||||
// Data URI links
|
||||
if ( isset( $url['scheme'] ) && 'data' === $url['scheme'] )
|
||||
continue;
|
||||
|
||||
// Remove large (and likely invalid) links
|
||||
if ( 4096 < strlen( $link_raw ) )
|
||||
continue;
|
||||
|
||||
// Build a simple form of the URL so we can compare it to ones we found in IMAGES or SHORTCODES and exclude those
|
||||
$simple_url = $url['scheme'] . '://' . $url['host'] . ( ! empty( $url['path'] ) ? $url['path'] : '' );
|
||||
if ( isset( $extracted['image']['url'] ) ) {
|
||||
if ( in_array( $simple_url, (array) $extracted['image']['url'] ) )
|
||||
continue;
|
||||
}
|
||||
|
||||
list( $proto, $link_all_but_proto ) = explode( '://', $link_raw );
|
||||
|
||||
// Build a reversed hostname
|
||||
$host_parts = array_reverse( explode( '.', $url['host'] ) );
|
||||
$host_reversed = '';
|
||||
foreach ( $host_parts as $part ) {
|
||||
$host_reversed .= ( ! empty( $host_reversed ) ? '.' : '' ) . $part;
|
||||
}
|
||||
|
||||
$link_analyzed = '';
|
||||
if ( !empty( $url['path'] ) ) {
|
||||
// The whole path (no query args or fragments)
|
||||
$path = substr( $url['path'], 1 ); // strip the leading '/'
|
||||
$link_analyzed .= ( ! empty( $link_analyzed ) ? ' ' : '' ) . $path;
|
||||
|
||||
// The path split by /
|
||||
$path_split = explode( '/', $path );
|
||||
if ( count( $path_split ) > 1 ) {
|
||||
$link_analyzed .= ' ' . implode( ' ', $path_split );
|
||||
}
|
||||
|
||||
// The fragment
|
||||
if ( ! empty( $url['fragment'] ) )
|
||||
$link_analyzed .= ( ! empty( $link_analyzed ) ? ' ' : '' ) . $url['fragment'];
|
||||
}
|
||||
|
||||
// @todo Check unique before adding
|
||||
$links[] = array(
|
||||
'url' => $link_all_but_proto,
|
||||
'host_reversed' => $host_reversed,
|
||||
'host' => $url['host'],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$link_count = count( $links );
|
||||
if ( $link_count ) {
|
||||
$extracted[ 'link' ] = $links;
|
||||
if ( !isset( $extracted['has'] ) )
|
||||
$extracted['has'] = array();
|
||||
$extracted['has']['link'] = $link_count;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------- EMBEDS ------------------------------
|
||||
|
||||
//Embeds are just individual links on their own line
|
||||
if ( self::EMBEDS & $what_to_extract ) {
|
||||
|
||||
if ( !function_exists( '_wp_oembed_get_object' ) )
|
||||
include( ABSPATH . WPINC . '/class-oembed.php' );
|
||||
|
||||
// get an oembed object
|
||||
$oembed = _wp_oembed_get_object();
|
||||
|
||||
// Grab any links on their own lines that may be embeds
|
||||
if ( preg_match_all( '|^\s*(https?://[^\s"]+)\s*$|im', $content, $matches ) ) {
|
||||
|
||||
// To hold the extracted stuff we find
|
||||
$embeds = array();
|
||||
|
||||
foreach ( $matches[1] as $link_raw ) {
|
||||
$url = parse_url( $link_raw );
|
||||
|
||||
list( $proto, $link_all_but_proto ) = explode( '://', $link_raw );
|
||||
|
||||
// Check whether this "link" is really an embed.
|
||||
foreach ( $oembed->providers as $matchmask => $data ) {
|
||||
list( $providerurl, $regex ) = $data;
|
||||
|
||||
// Turn the asterisk-type provider URLs into regex
|
||||
if ( !$regex ) {
|
||||
$matchmask = '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $matchmask ), '#' ) ) . '#i';
|
||||
$matchmask = preg_replace( '|^#http\\\://|', '#https?\://', $matchmask );
|
||||
}
|
||||
|
||||
if ( preg_match( $matchmask, $link_raw ) ) {
|
||||
$provider = str_replace( '{format}', 'json', $providerurl ); // JSON is easier to deal with than XML
|
||||
$embeds[] = $link_all_but_proto; // @todo Check unique before adding
|
||||
|
||||
// @todo Try to get ID's for the ones we care about (shortcode_keepers)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $embeds ) ) {
|
||||
if ( !isset( $extracted['has'] ) )
|
||||
$extracted['has'] = array();
|
||||
$extracted['has']['embed'] = count( $embeds );
|
||||
$extracted['embed'] = array( 'url' => array() );
|
||||
foreach ( $embeds as $e )
|
||||
$extracted['embed']['url'][] = $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $extracted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $post A post object
|
||||
* @param $args (array) Optional args, see defaults list for details
|
||||
* @returns array Returns an array of all images meeting the specified criteria in $args
|
||||
*
|
||||
* Uses Jetpack Post Images
|
||||
*/
|
||||
private static function get_image_fields( $post, $args = array() ) {
|
||||
|
||||
$defaults = array(
|
||||
'width' => 200, // Required minimum width (if possible to determine)
|
||||
'height' => 200, // Required minimum height (if possible to determine)
|
||||
);
|
||||
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
|
||||
$image_list = array();
|
||||
$image_booleans = array();
|
||||
$image_booleans['gallery'] = 0;
|
||||
|
||||
$from_featured_image = Jetpack_PostImages::from_thumbnail( $post->ID, $args['width'], $args['height'] );
|
||||
if ( !empty( $from_featured_image ) ) {
|
||||
$srcs = wp_list_pluck( $from_featured_image, 'src' );
|
||||
$image_list = array_merge( $image_list, $srcs );
|
||||
}
|
||||
|
||||
$from_slideshow = Jetpack_PostImages::from_slideshow( $post->ID, $args['width'], $args['height'] );
|
||||
if ( !empty( $from_slideshow ) ) {
|
||||
$srcs = wp_list_pluck( $from_slideshow, 'src' );
|
||||
$image_list = array_merge( $image_list, $srcs );
|
||||
}
|
||||
|
||||
$from_gallery = Jetpack_PostImages::from_gallery( $post->ID );
|
||||
if ( !empty( $from_gallery ) ) {
|
||||
$srcs = wp_list_pluck( $from_gallery, 'src' );
|
||||
$image_list = array_merge( $image_list, $srcs );
|
||||
$image_booleans['gallery']++; // @todo This count isn't correct, will only every count 1
|
||||
}
|
||||
|
||||
// @todo Can we check width/height of these efficiently? Could maybe use query args at least, before we strip them out
|
||||
$image_list = Jetpack_Media_Meta_Extractor::get_images_from_html( $post->post_content, $image_list );
|
||||
|
||||
return Jetpack_Media_Meta_Extractor::build_image_struct( $image_list );
|
||||
}
|
||||
|
||||
public static function extract_images_from_content( $content, $image_list ) {
|
||||
$image_list = Jetpack_Media_Meta_Extractor::get_images_from_html( $content, $image_list );
|
||||
return Jetpack_Media_Meta_Extractor::build_image_struct( $image_list );
|
||||
}
|
||||
|
||||
public static function build_image_struct( $image_list ) {
|
||||
if ( ! empty( $image_list ) ) {
|
||||
$retval = array( 'image' => array() );
|
||||
$image_list = array_unique( $image_list );
|
||||
foreach ( $image_list as $img ) {
|
||||
$retval['image'][] = array( 'url' => $img );
|
||||
}
|
||||
$image_booleans['image'] = count( $retval['image'] );
|
||||
if ( ! empty( $image_booleans ) )
|
||||
$retval['has'] = $image_booleans;
|
||||
return $retval;
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $html Some markup, possibly containing image tags
|
||||
* @param array $images_already_extracted (just an array of image URLs without query strings, no special structure), used for de-duplication
|
||||
* @return array Image URLs extracted from the HTML, stripped of query params and de-duped
|
||||
*/
|
||||
public static function get_images_from_html( $html, $images_already_extracted ) {
|
||||
$image_list = $images_already_extracted;
|
||||
$from_html = Jetpack_PostImages::from_html( $html );
|
||||
if ( !empty( $from_html ) ) {
|
||||
$srcs = wp_list_pluck( $from_html, 'src' );
|
||||
foreach( $srcs as $image_url ) {
|
||||
if ( ( $src = parse_url( $image_url ) ) && isset( $src['scheme'], $src['host'], $src['path'] ) ) {
|
||||
// Rebuild the URL without the query string
|
||||
$queryless = $src['scheme'] . '://' . $src['host'] . $src['path'];
|
||||
} elseif ( $length = strpos( $image_url, '?' ) ) {
|
||||
// If parse_url() didn't work, strip off the query string the old fashioned way
|
||||
$queryless = substr( $image_url, 0, $length );
|
||||
} else {
|
||||
// Failing that, there was no spoon! Err ... query string!
|
||||
$queryless = $image_url;
|
||||
}
|
||||
|
||||
// Discard URLs that are longer then 4KB, these are likely data URIs or malformed HTML.
|
||||
if ( 4096 < strlen( $queryless ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! in_array( $queryless, $image_list ) ) {
|
||||
$image_list[] = $queryless;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $image_list;
|
||||
}
|
||||
|
||||
private static function get_stripped_content( $content ) {
|
||||
$clean_content = strip_tags( $content );
|
||||
$clean_content = html_entity_decode( $clean_content );
|
||||
//completely strip shortcodes and any content they enclose
|
||||
$clean_content = strip_shortcodes( $clean_content );
|
||||
return $clean_content;
|
||||
}
|
||||
}
|
281
plugins/jetpack/_inc/lib/class.media-summary.php
Normal file
281
plugins/jetpack/_inc/lib/class.media-summary.php
Normal file
|
@ -0,0 +1,281 @@
|
|||
<?php
|
||||
/**
|
||||
* Class Jetpack_Media_Summary
|
||||
*
|
||||
* embed [video] > gallery > image > text
|
||||
*/
|
||||
class Jetpack_Media_Summary {
|
||||
|
||||
static function get( $post_id, $blog_id = 0, $args = array() ) {
|
||||
$defaults = array(
|
||||
'max_words' => 16,
|
||||
'max_chars' => 256,
|
||||
);
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
|
||||
$switched = false;
|
||||
if ( !empty( $blog_id ) && $blog_id != get_current_blog_id() && function_exists( 'switch_to_blog' ) ) {
|
||||
switch_to_blog( $blog_id );
|
||||
$switched = true;
|
||||
} else {
|
||||
$blog_id = get_current_blog_id();
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'Jetpack_Media_Meta_Extractor' ) ) {
|
||||
jetpack_require_lib( 'class.media-extractor' );
|
||||
}
|
||||
|
||||
$post = get_post( $post_id );
|
||||
$permalink = get_permalink( $post_id );
|
||||
|
||||
$return = array(
|
||||
'type' => 'standard',
|
||||
'permalink' => $permalink,
|
||||
'image' => '',
|
||||
'excerpt' => '',
|
||||
'word_count' => 0,
|
||||
'secure' => array(
|
||||
'image' => '',
|
||||
),
|
||||
'count' => array(
|
||||
'image' => 0,
|
||||
'video' => 0,
|
||||
'word' => 0,
|
||||
'link' => 0,
|
||||
),
|
||||
);
|
||||
|
||||
if ( empty( $post->post_password ) ) {
|
||||
$return['excerpt'] = self::get_excerpt( $post->post_content, $post->post_excerpt, $args['max_words'], $args['max_chars'] );
|
||||
$return['count']['word'] = self::get_word_count( $post->post_content );
|
||||
$return['count']['word_remaining'] = self::get_word_remaining_count( $post->post_content, $return['excerpt'] );
|
||||
$return['count']['link'] = self::get_link_count( $post->post_content );
|
||||
}
|
||||
|
||||
$extract = Jetpack_Media_Meta_Extractor::extract( $blog_id, $post_id, Jetpack_Media_Meta_Extractor::ALL );
|
||||
|
||||
if ( empty( $extract['has'] ) )
|
||||
return $return;
|
||||
|
||||
// Prioritize [some] video embeds
|
||||
if ( !empty( $extract['has']['shortcode'] ) ) {
|
||||
foreach ( $extract['shortcode'] as $type => $data ) {
|
||||
switch ( $type ) {
|
||||
case 'wpvideo':
|
||||
if ( 0 == $return['count']['video'] ) {
|
||||
$return['type'] = 'video';
|
||||
$return['video'] = esc_url_raw( 'http://s0.videopress.com/player.swf?guid=' . $extract['shortcode']['wpvideo']['id'][0] . '&isDynamicSeeking=true' );
|
||||
$return['image'] = self::get_video_poster( 'videopress', $extract['shortcode']['wpvideo']['id'][0] );
|
||||
$return['secure']['video'] = preg_replace( '@http://[^\.]+.videopress.com/@', 'https://v0.wordpress.com/', $return['video'] );
|
||||
$return['secure']['image'] = str_replace( 'http://videos.videopress.com', 'https://videos.files.wordpress.com', $return['image'] );
|
||||
}
|
||||
$return['count']['video']++;
|
||||
break;
|
||||
case 'youtube':
|
||||
if ( 0 == $return['count']['video'] ) {
|
||||
$return['type'] = 'video';
|
||||
$return['video'] = esc_url_raw( 'http://www.youtube.com/watch?feature=player_embedded&v=' . $extract['shortcode']['youtube']['id'][0] );
|
||||
$return['image'] = self::get_video_poster( 'youtube', $extract['shortcode']['youtube']['id'][0] );
|
||||
$return['secure']['video'] = self::https( $return['video'] );
|
||||
$return['secure']['image'] = self::https( $return['image'] );
|
||||
}
|
||||
$return['count']['video']++;
|
||||
break;
|
||||
case 'vimeo':
|
||||
if ( 0 == $return['count']['video'] ) {
|
||||
$return['type'] = 'video';
|
||||
$return['video'] = esc_url_raw( 'http://vimeo.com/' . $extract['shortcode']['vimeo']['id'][0] );
|
||||
$return['secure']['video'] = self::https( $return['video'] );
|
||||
|
||||
$poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true );
|
||||
if ( !empty( $poster_image ) ) {
|
||||
$return['image'] = $poster_image;
|
||||
$poster_url_parts = parse_url( $poster_image );
|
||||
$return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path'];
|
||||
}
|
||||
}
|
||||
$return['count']['video']++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( !empty( $extract['has']['embed'] ) ) {
|
||||
foreach( $extract['embed']['url'] as $embed ) {
|
||||
if ( preg_match( '/((youtube|vimeo|dailymotion)\.com|youtu.be)/', $embed ) ) {
|
||||
if ( 0 == $return['count']['video'] ) {
|
||||
$return['type'] = 'video';
|
||||
$return['video'] = 'http://' . $embed;
|
||||
$return['secure']['video'] = self::https( $return['video'] );
|
||||
if ( false !== strpos( $embed, 'youtube' ) ) {
|
||||
$return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) );
|
||||
$return['secure']['image'] = self::https( $return['image'] );
|
||||
} else if ( false !== strpos( $embed, 'youtu.be' ) ) {
|
||||
$youtube_id = jetpack_get_youtube_id( $return['video'] );
|
||||
$return['video'] = 'http://youtube.com/watch?v=' . $youtube_id . '&feature=youtu.be';
|
||||
$return['secure']['video'] = self::https( $return['video'] );
|
||||
$return['image'] = self::get_video_poster( 'youtube', jetpack_get_youtube_id( $return['video'] ) );
|
||||
$return['secure']['image'] = self::https( $return['image'] );
|
||||
} else if ( false !== strpos( $embed, 'vimeo' ) ) {
|
||||
$poster_image = get_post_meta( $post_id, 'vimeo_poster_image', true );
|
||||
if ( !empty( $poster_image ) ) {
|
||||
$return['image'] = $poster_image;
|
||||
$poster_url_parts = parse_url( $poster_image );
|
||||
$return['secure']['image'] = 'https://secure-a.vimeocdn.com' . $poster_url_parts['path'];
|
||||
}
|
||||
} else if ( false !== strpos( $embed, 'dailymotion' ) ) {
|
||||
$return['image'] = str_replace( 'dailymotion.com/video/','dailymotion.com/thumbnail/video/', $embed );
|
||||
$return['image'] = parse_url( $return['image'], PHP_URL_SCHEME ) === null ? 'http://' . $return['image'] : $return['image'];
|
||||
$return['secure']['image'] = self::https( $return['image'] );
|
||||
}
|
||||
|
||||
}
|
||||
$return['count']['video']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do we really want to make the video the primary focus of the post?
|
||||
if ( 'video' == $return['type'] ) {
|
||||
$content = wpautop( strip_tags( $post->post_content ) );
|
||||
$paragraphs = explode( '</p>', $content );
|
||||
$number_of_paragraphs = 0;
|
||||
|
||||
foreach ( $paragraphs as $i => $paragraph ) {
|
||||
// Don't include blank lines as a paragraph
|
||||
if ( '' == trim( $paragraph ) ) {
|
||||
unset( $paragraphs[$i] );
|
||||
continue;
|
||||
}
|
||||
$number_of_paragraphs++;
|
||||
}
|
||||
|
||||
$number_of_paragraphs = $number_of_paragraphs - $return['count']['video']; // subtract amount for videos..
|
||||
|
||||
// More than 2 paragraph? The video is not the primary focus so we can do some more analysis
|
||||
if ( $number_of_paragraphs > 2 )
|
||||
$return['type'] = 'standard';
|
||||
}
|
||||
|
||||
// If we don't have any prioritized embed...
|
||||
if ( 'standard' == $return['type'] ) {
|
||||
if ( ( ! empty( $extract['has']['gallery'] ) || ! empty( $extract['shortcode']['gallery']['count'] ) ) && ! empty( $extract['image'] ) ) {
|
||||
//... Then we prioritize galleries first (multiple images returned)
|
||||
$return['type'] = 'gallery';
|
||||
$return['images'] = $extract['image'];
|
||||
foreach ( $return['images'] as $image ) {
|
||||
$return['secure']['images'][] = array( 'url' => self::ssl_img( $image['url'] ) );
|
||||
$return['count']['image']++;
|
||||
}
|
||||
} else if ( ! empty( $extract['has']['image'] ) ) {
|
||||
// ... Or we try and select a single image that would make sense
|
||||
$content = wpautop( strip_tags( $post->post_content ) );
|
||||
$paragraphs = explode( '</p>', $content );
|
||||
$number_of_paragraphs = 0;
|
||||
|
||||
foreach ( $paragraphs as $i => $paragraph ) {
|
||||
// Don't include 'actual' captions as a paragraph
|
||||
if ( false !== strpos( $paragraph, '[caption' ) ) {
|
||||
unset( $paragraphs[$i] );
|
||||
continue;
|
||||
}
|
||||
// Don't include blank lines as a paragraph
|
||||
if ( '' == trim( $paragraph ) ) {
|
||||
unset( $paragraphs[$i] );
|
||||
continue;
|
||||
}
|
||||
$number_of_paragraphs++;
|
||||
}
|
||||
|
||||
$return['image'] = $extract['image'][0]['url'];
|
||||
$return['secure']['image'] = self::ssl_img( $return['image'] );
|
||||
$return['count']['image']++;
|
||||
|
||||
if ( $number_of_paragraphs <= 2 && 1 == count( $extract['image'] ) ) {
|
||||
// If we have lots of text or images, let's not treat it as an image post, but return its first image
|
||||
$return['type'] = 'image';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $switched ) {
|
||||
restore_current_blog();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
static function https( $str ) {
|
||||
return str_replace( 'http://', 'https://', $str );
|
||||
}
|
||||
|
||||
static function ssl_img( $url ) {
|
||||
if ( false !== strpos( $url, 'files.wordpress.com' ) ) {
|
||||
return self::https( $url );
|
||||
} else {
|
||||
return self::https( jetpack_photon_url( $url ) );
|
||||
}
|
||||
}
|
||||
|
||||
static function get_video_poster( $type, $id ) {
|
||||
if ( 'videopress' == $type ) {
|
||||
if ( function_exists( 'video_get_highest_resolution_image_url' ) ) {
|
||||
return video_get_highest_resolution_image_url( $id );
|
||||
} else if ( class_exists( 'VideoPress_Video' ) ) {
|
||||
$video = new VideoPress_Video( $id );
|
||||
return $video->poster_frame_uri;
|
||||
}
|
||||
} else if ( 'youtube' == $type ) {
|
||||
return 'http://img.youtube.com/vi/'.$id.'/0.jpg';
|
||||
}
|
||||
}
|
||||
|
||||
static function clean_text( $text ) {
|
||||
return trim(
|
||||
preg_replace(
|
||||
'/[\s]+/',
|
||||
' ',
|
||||
preg_replace(
|
||||
'@https?://[\S]+@',
|
||||
'',
|
||||
strip_shortcodes(
|
||||
strip_tags(
|
||||
$text
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static function get_excerpt( $post_content, $post_excerpt, $max_words = 16, $max_chars = 256 ) {
|
||||
if ( function_exists( 'wpcom_enhanced_excerpt_extract_excerpt' ) ) {
|
||||
return self::clean_text( wpcom_enhanced_excerpt_extract_excerpt( array(
|
||||
'text' => $post_content,
|
||||
'excerpt_only' => true,
|
||||
'show_read_more' => false,
|
||||
'max_words' => $max_words,
|
||||
'max_chars' => $max_chars,
|
||||
'read_more_threshold' => 25,
|
||||
) ) );
|
||||
} else {
|
||||
|
||||
/** This filter is documented in core/src/wp-includes/post-template.php */
|
||||
$post_excerpt = apply_filters( 'get_the_excerpt', $post_excerpt );
|
||||
return self::clean_text( $post_excerpt );
|
||||
}
|
||||
}
|
||||
|
||||
static function get_word_count( $post_content ) {
|
||||
return str_word_count( self::clean_text( $post_content ) );
|
||||
}
|
||||
|
||||
static function get_word_remaining_count( $post_content, $excerpt_content ) {
|
||||
return str_word_count( self::clean_text( $post_content ) ) - str_word_count( self::clean_text( $excerpt_content ) );
|
||||
}
|
||||
|
||||
static function get_link_count( $post_content ) {
|
||||
return preg_match_all( '/\<a[\> ]/', $post_content, $matches );
|
||||
}
|
||||
}
|
6
plugins/jetpack/_inc/lib/markdown/0-load.php
Normal file
6
plugins/jetpack/_inc/lib/markdown/0-load.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
if ( ! class_exists( 'MarkdownExtra_Parser' ) )
|
||||
jetpack_require_lib( 'markdown/extra' );
|
||||
|
||||
jetpack_require_lib( 'markdown/gfm' );
|
19
plugins/jetpack/_inc/lib/markdown/README.md
Normal file
19
plugins/jetpack/_inc/lib/markdown/README.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Markdown parsing library
|
||||
|
||||
Contains two libraries:
|
||||
|
||||
* `/extra`
|
||||
- Gives you `MardownExtra_Parser` and `Markdown_Parser`
|
||||
- Docs at http://michelf.ca/projects/php-markdown/extra/
|
||||
|
||||
* `/gfm` -- Github Flavored MArkdown
|
||||
- Gives you `WPCom_GHF_Markdown_Parser`
|
||||
- It has the same interface as `MarkdownExtra_Parser`
|
||||
- Adds support for fenced code blocks: https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks
|
||||
- By default it replaces them with a code shortcode
|
||||
- You can change this using the `$use_code_shortcode` member variable
|
||||
- You can change the code shortcode wrapping with `$shortcode_start` and `$shortcode_end` member variables
|
||||
- The `$preserve_shortcodes` member variable will preserve all registered shortcodes untouched. Requires WordPress to be loaded for `get_shortcode_regex()`
|
||||
- The `$preserve_latex` member variable will preserve oldskool $latex yer-latex$ codes untouched.
|
||||
- The `$strip_paras` member variable will strip <p> tags because that's what WordPress likes.
|
||||
- See `WPCom_GHF_Markdown_Parser::__construct()` for how the above member variable defaults are set.
|
3194
plugins/jetpack/_inc/lib/markdown/extra.php
Normal file
3194
plugins/jetpack/_inc/lib/markdown/extra.php
Normal file
File diff suppressed because it is too large
Load diff
389
plugins/jetpack/_inc/lib/markdown/gfm.php
Normal file
389
plugins/jetpack/_inc/lib/markdown/gfm.php
Normal file
|
@ -0,0 +1,389 @@
|
|||
<?php
|
||||
/**
|
||||
* GitHub-Flavoured Markdown. Inspired by Evan's plugin, but modified.
|
||||
*
|
||||
* @author Evan Solomon
|
||||
* @author Matt Wiebe <wiebe@automattic.com>
|
||||
* @link https://github.com/evansolomon/wp-github-flavored-markdown-comments
|
||||
*
|
||||
* Add a few extras from GitHub's Markdown implementation. Must be used in a WordPress environment.
|
||||
*/
|
||||
|
||||
class WPCom_GHF_Markdown_Parser extends MarkdownExtra_Parser {
|
||||
|
||||
/**
|
||||
* Hooray somewhat arbitrary numbers that are fearful of 1.0.x.
|
||||
*/
|
||||
const WPCOM_GHF_MARDOWN_VERSION = '0.9.0';
|
||||
|
||||
/**
|
||||
* Use a [code] shortcode when encountering a fenced code block
|
||||
* @var boolean
|
||||
*/
|
||||
public $use_code_shortcode = true;
|
||||
|
||||
/**
|
||||
* Preserve shortcodes, untouched by Markdown.
|
||||
* This requires use within a WordPress installation.
|
||||
* @var boolean
|
||||
*/
|
||||
public $preserve_shortcodes = true;
|
||||
|
||||
/**
|
||||
* Preserve the legacy $latex your-latex-code-here$ style
|
||||
* LaTeX markup
|
||||
*/
|
||||
public $preserve_latex = true;
|
||||
|
||||
/**
|
||||
* Preserve single-line <code> blocks.
|
||||
* @var boolean
|
||||
*/
|
||||
public $preserve_inline_code_blocks = true;
|
||||
|
||||
/**
|
||||
* Strip paragraphs from the output. This is the right default for WordPress,
|
||||
* which generally wants to create its own paragraphs with `wpautop`
|
||||
* @var boolean
|
||||
*/
|
||||
public $strip_paras = true;
|
||||
|
||||
// Will run through sprintf - you can supply your own syntax if you want
|
||||
public $shortcode_start = '[code lang=%s]';
|
||||
public $shortcode_end = '[/code]';
|
||||
|
||||
// Stores shortcodes we remove and then replace
|
||||
protected $preserve_text_hash = array();
|
||||
|
||||
/**
|
||||
* Set environment defaults based on presence of key functions/classes.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->use_code_shortcode = class_exists( 'SyntaxHighlighter' );
|
||||
$this->preserve_shortcodes = function_exists( 'get_shortcode_regex' );
|
||||
$this->preserve_latex = function_exists( 'latex_markup' );
|
||||
$this->strip_paras = function_exists( 'wpautop' );
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload to specify heading styles only if the hash has space(s) after it. This is actually in keeping with
|
||||
* the documentation and eases the semantic overload of the hash character.
|
||||
* #Will Not Produce a Heading 1
|
||||
* # This Will Produce a Heading 1
|
||||
*
|
||||
* @param string $text Markdown text
|
||||
* @return string HTML-transformed text
|
||||
*/
|
||||
public function transform( $text ) {
|
||||
// Preserve anything inside a single-line <code> element
|
||||
if ( $this->preserve_inline_code_blocks ) {
|
||||
$text = $this->single_line_code_preserve( $text );
|
||||
}
|
||||
// Remove all shortcodes so their interiors are left intact
|
||||
if ( $this->preserve_shortcodes ) {
|
||||
$text = $this->shortcode_preserve( $text );
|
||||
}
|
||||
// Remove legacy LaTeX so it's left intact
|
||||
if ( $this->preserve_latex ) {
|
||||
$text = $this->latex_preserve( $text );
|
||||
}
|
||||
|
||||
// escape line-beginning # chars that do not have a space after them.
|
||||
$text = preg_replace_callback( '|^#{1,6}( )?|um', array( $this, '_doEscapeForHashWithoutSpacing' ), $text );
|
||||
|
||||
/**
|
||||
* Allow third-party plugins to define custom patterns that won't be processed by Markdown.
|
||||
*
|
||||
* @module markdown
|
||||
*
|
||||
* @since 3.9.2
|
||||
*
|
||||
* @param array $custom_patterns Array of custom patterns to be ignored by Markdown.
|
||||
*/
|
||||
$custom_patterns = apply_filters( 'jetpack_markdown_preserve_pattern', array() );
|
||||
if ( is_array( $custom_patterns ) && ! empty( $custom_patterns ) ) {
|
||||
foreach ( $custom_patterns as $pattern ) {
|
||||
$text = preg_replace_callback( $pattern, array( $this, '_doRemoveText'), $text );
|
||||
}
|
||||
}
|
||||
|
||||
// run through core Markdown
|
||||
$text = parent::transform( $text );
|
||||
|
||||
// Occasionally Markdown Extra chokes on a para structure, producing odd paragraphs.
|
||||
$text = str_replace( "<p><</p>\n\n<p>p>", '<p>', $text );
|
||||
|
||||
// put start-of-line # chars back in place
|
||||
$text = $this->restore_leading_hash( $text );
|
||||
|
||||
// Strip paras if set
|
||||
if ( $this->strip_paras ) {
|
||||
$text = $this->unp( $text );
|
||||
}
|
||||
|
||||
// Restore preserved things like shortcodes/LaTeX
|
||||
$text = $this->do_restore( $text );
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents blocks like <code>__this__</code> from turning into <code><strong>this</strong></code>
|
||||
* @param string $text Text that may need preserving
|
||||
* @return string Text that was preserved if needed
|
||||
*/
|
||||
public function single_line_code_preserve( $text ) {
|
||||
return preg_replace_callback( '|<code\b[^>]*>(.*?)</code>|', array( $this, 'do_single_line_code_preserve' ), $text );
|
||||
}
|
||||
|
||||
/**
|
||||
* Regex callback for inline code presevation
|
||||
* @param array $matches Regex matches
|
||||
* @return string Hashed content for later restoration
|
||||
*/
|
||||
public function do_single_line_code_preserve( $matches ) {
|
||||
return '<code>' . $this->hash_block( $matches[1] ) . '</code>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Preserve code block contents by HTML encoding them. Useful before getting to KSES stripping.
|
||||
* @param string $text Markdown/HTML content
|
||||
* @return string Markdown/HTML content with escaped code blocks
|
||||
*/
|
||||
public function codeblock_preserve( $text ) {
|
||||
return preg_replace_callback( "/^([`~]{3})([^`\n]+)?\n([^`~]+)(\\1)/m", array( $this, 'do_codeblock_preserve' ), $text );
|
||||
}
|
||||
|
||||
/**
|
||||
* Regex callback for code block preservation.
|
||||
* @param array $matches Regex matches
|
||||
* @return string Codeblock with escaped interior
|
||||
*/
|
||||
public function do_codeblock_preserve( $matches ) {
|
||||
$block = stripslashes( $matches[3] );
|
||||
$block = esc_html( $block );
|
||||
$block = str_replace( '\\', '\\\\', $block );
|
||||
$open = $matches[1] . $matches[2] . "\n";
|
||||
return $open . $block . $matches[4];
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore previously preserved (i.e. escaped) code block contents.
|
||||
* @param string $text Markdown/HTML content with escaped code blocks
|
||||
* @return string Markdown/HTML content
|
||||
*/
|
||||
public function codeblock_restore( $text ) {
|
||||
return preg_replace_callback( "/^([`~]{3})([^`\n]+)?\n([^`~]+)(\\1)/m", array( $this, 'do_codeblock_restore' ), $text );
|
||||
}
|
||||
|
||||
/**
|
||||
* Regex callback for code block restoration (unescaping).
|
||||
* @param array $matches Regex matches
|
||||
* @return string Codeblock with unescaped interior
|
||||
*/
|
||||
public function do_codeblock_restore( $matches ) {
|
||||
$block = html_entity_decode( $matches[3], ENT_QUOTES );
|
||||
$open = $matches[1] . $matches[2] . "\n";
|
||||
return $open . $block . $matches[4];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to preserve legacy LaTeX like $latex some-latex-text $
|
||||
* @param string $text Text in which to preserve LaTeX
|
||||
* @return string Text with LaTeX replaced by a hash that will be restored later
|
||||
*/
|
||||
protected function latex_preserve( $text ) {
|
||||
// regex from latex_remove()
|
||||
$regex = '%
|
||||
\$latex(?:=\s*|\s+)
|
||||
((?:
|
||||
[^$]+ # Not a dollar
|
||||
|
|
||||
(?<=(?<!\\\\)\\\\)\$ # Dollar preceded by exactly one slash
|
||||
)+)
|
||||
(?<!\\\\)\$ # Dollar preceded by zero slashes
|
||||
%ix';
|
||||
$text = preg_replace_callback( $regex, array( $this, '_doRemoveText'), $text );
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to preserve WP shortcodes from being formatted by Markdown in any way.
|
||||
* @param string $text Text in which to preserve shortcodes
|
||||
* @return string Text with shortcodes replaced by a hash that will be restored later
|
||||
*/
|
||||
protected function shortcode_preserve( $text ) {
|
||||
$text = preg_replace_callback( $this->get_shortcode_regex(), array( $this, '_doRemoveText' ), $text );
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores any text preserved by $this->hash_block()
|
||||
* @param string $text Text that may have hashed preservation placeholders
|
||||
* @return string Text with hashed preseravtion placeholders replaced by original text
|
||||
*/
|
||||
protected function do_restore( $text ) {
|
||||
foreach( $this->preserve_text_hash as $hash => $value ) {
|
||||
$placeholder = $this->hash_maker( $hash );
|
||||
$text = str_replace( $placeholder, $value, $text );
|
||||
}
|
||||
// reset the hash
|
||||
$this->preserve_text_hash = array();
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Regex callback for text preservation
|
||||
* @param array $m Regex $matches array
|
||||
* @return string A placeholder that will later be replaced by the original text
|
||||
*/
|
||||
protected function _doRemoveText( $m ) {
|
||||
return $this->hash_block( $m[0] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to store a text block for later restoration.
|
||||
* @param string $text Text to preserve for later
|
||||
* @return string Placeholder that will be swapped out later for the original text
|
||||
*/
|
||||
protected function hash_block( $text ) {
|
||||
$hash = md5( $text );
|
||||
$this->preserve_text_hash[ $hash ] = $text;
|
||||
$placeholder = $this->hash_maker( $hash );
|
||||
return $placeholder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Less glamorous than the Keymaker
|
||||
* @param string $hash An md5 hash
|
||||
* @return string A placeholder hash
|
||||
*/
|
||||
protected function hash_maker( $hash ) {
|
||||
return 'MARKDOWN_HASH' . $hash . 'MARKDOWN_HASH';
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove bare <p> elements. <p>s with attributes will be preserved.
|
||||
* @param string $text HTML content
|
||||
* @return string <p>-less content
|
||||
*/
|
||||
public function unp( $text ) {
|
||||
return preg_replace( "#<p>(.*?)</p>(\n|$)#ums", '$1$2', $text );
|
||||
}
|
||||
|
||||
/**
|
||||
* A regex of all shortcodes currently registered by the current
|
||||
* WordPress installation
|
||||
* @uses get_shortcode_regex()
|
||||
* @return string A regex for grabbing shortcodes.
|
||||
*/
|
||||
protected function get_shortcode_regex() {
|
||||
$pattern = get_shortcode_regex();
|
||||
|
||||
// don't match markdown link anchors that could be mistaken for shortcodes.
|
||||
$pattern .= '(?!\()';
|
||||
|
||||
return "/$pattern/s";
|
||||
}
|
||||
|
||||
/**
|
||||
* Since we escape unspaced #Headings, put things back later.
|
||||
* @param string $text text with a leading escaped hash
|
||||
* @return string text with leading hashes unescaped
|
||||
*/
|
||||
protected function restore_leading_hash( $text ) {
|
||||
return preg_replace( "/^(<p>)?(#|\\\\#)/um", "$1#", $text );
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload to support ```-fenced code blocks for pre-Markdown Extra 1.2.8
|
||||
* https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks
|
||||
*/
|
||||
public function doFencedCodeBlocks( $text ) {
|
||||
// If we're at least at 1.2.8, native fenced code blocks are in.
|
||||
// Below is just copied from it in case we somehow got loaded on
|
||||
// top of someone else's Markdown Extra
|
||||
if ( version_compare( MARKDOWNEXTRA_VERSION, '1.2.8', '>=' ) )
|
||||
return parent::doFencedCodeBlocks( $text );
|
||||
|
||||
#
|
||||
# Adding the fenced code block syntax to regular Markdown:
|
||||
#
|
||||
# ~~~
|
||||
# Code block
|
||||
# ~~~
|
||||
#
|
||||
$less_than_tab = $this->tab_width;
|
||||
|
||||
$text = preg_replace_callback('{
|
||||
(?:\n|\A)
|
||||
# 1: Opening marker
|
||||
(
|
||||
(?:~{3,}|`{3,}) # 3 or more tildes/backticks.
|
||||
)
|
||||
[ ]*
|
||||
(?:
|
||||
\.?([-_:a-zA-Z0-9]+) # 2: standalone class name
|
||||
|
|
||||
'.$this->id_class_attr_catch_re.' # 3: Extra attributes
|
||||
)?
|
||||
[ ]* \n # Whitespace and newline following marker.
|
||||
|
||||
# 4: Content
|
||||
(
|
||||
(?>
|
||||
(?!\1 [ ]* \n) # Not a closing marker.
|
||||
.*\n+
|
||||
)+
|
||||
)
|
||||
|
||||
# Closing marker.
|
||||
\1 [ ]* (?= \n )
|
||||
}xm',
|
||||
array($this, '_doFencedCodeBlocks_callback'), $text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for pre-processing start of line hashes to slyly escape headings that don't
|
||||
* have a leading space
|
||||
* @param array $m preg_match matches
|
||||
* @return string possibly escaped start of line hash
|
||||
*/
|
||||
public function _doEscapeForHashWithoutSpacing( $m ) {
|
||||
if ( ! isset( $m[1] ) )
|
||||
$m[0] = '\\' . $m[0];
|
||||
return $m[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload to support Viper's [code] shortcode. Because awesome.
|
||||
*/
|
||||
public function _doFencedCodeBlocks_callback( $matches ) {
|
||||
// in case we have some escaped leading hashes right at the start of the block
|
||||
$matches[4] = $this->restore_leading_hash( $matches[4] );
|
||||
// just MarkdownExtra_Parser if we're not going ultra-deluxe
|
||||
if ( ! $this->use_code_shortcode ) {
|
||||
return parent::_doFencedCodeBlocks_callback( $matches );
|
||||
}
|
||||
|
||||
// default to a "text" class if one wasn't passed. Helps with encoding issues later.
|
||||
if ( empty( $matches[2] ) ) {
|
||||
$matches[2] = 'text';
|
||||
}
|
||||
|
||||
$classname =& $matches[2];
|
||||
$codeblock = preg_replace_callback('/^\n+/', array( $this, '_doFencedCodeBlocks_newlines' ), $matches[4] );
|
||||
|
||||
if ( $classname{0} == '.' )
|
||||
$classname = substr( $classname, 1 );
|
||||
|
||||
$codeblock = esc_html( $codeblock );
|
||||
$codeblock = sprintf( $this->shortcode_start, $classname ) . "\n{$codeblock}" . $this->shortcode_end;
|
||||
return "\n\n" . $this->hashBlock( $codeblock ). "\n\n";
|
||||
}
|
||||
|
||||
}
|
215
plugins/jetpack/_inc/lib/tonesque.php
Normal file
215
plugins/jetpack/_inc/lib/tonesque.php
Normal file
|
@ -0,0 +1,215 @@
|
|||
<?php
|
||||
/*
|
||||
Plugin Name: Tonesque
|
||||
Plugin URI: http://automattic.com/
|
||||
Description: Grab an average color representation from an image.
|
||||
Version: 1.0
|
||||
Author: Automattic, Matias Ventura
|
||||
Author URI: http://automattic.com/
|
||||
License: GNU General Public License v2 or later
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
*/
|
||||
|
||||
class Tonesque {
|
||||
|
||||
private $image_url = '';
|
||||
private $image_obj = NULL;
|
||||
private $color = '';
|
||||
|
||||
function __construct( $image_url ) {
|
||||
if ( ! class_exists( 'Jetpack_Color' ) )
|
||||
jetpack_require_lib( 'class.color' );
|
||||
|
||||
$this->image_url = esc_url_raw( $image_url );
|
||||
$this->image_url = trim( $this->image_url );
|
||||
/**
|
||||
* Allows any image URL to be passed in for $this->image_url.
|
||||
*
|
||||
* @module theme-tools
|
||||
*
|
||||
* @since 2.5.0
|
||||
*
|
||||
* @param string $image_url The URL to any image
|
||||
*/
|
||||
$this->image_url = apply_filters( 'tonesque_image_url', $this->image_url );
|
||||
|
||||
$this->image_obj = self::imagecreatefromurl( $this->image_url );
|
||||
}
|
||||
|
||||
public static function imagecreatefromurl( $image_url ) {
|
||||
// Grab the extension
|
||||
$file = strtolower( pathinfo( $image_url, PATHINFO_EXTENSION ) );
|
||||
$file = explode( '?', $file );
|
||||
$file = $file[ 0 ];
|
||||
|
||||
switch ( $file ) {
|
||||
case 'gif' :
|
||||
$image_obj = imagecreatefromgif( $image_url );
|
||||
break;
|
||||
case 'png' :
|
||||
$image_obj = imagecreatefrompng( $image_url );
|
||||
break;
|
||||
case 'jpg' :
|
||||
case 'jpeg' :
|
||||
$image_obj = imagecreatefromjpeg( $image_url );
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return $image_obj;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Construct object from image.
|
||||
*
|
||||
* @param optional $type (hex, rgb, hsl)
|
||||
* @return color as a string formatted as $type
|
||||
*
|
||||
*/
|
||||
function color( $type = 'hex' ) {
|
||||
// Bail if there is no image to work with
|
||||
if ( ! $this->image_obj )
|
||||
return false;
|
||||
|
||||
// Finds dominant color
|
||||
$color = self::grab_color();
|
||||
// Passes value to Color class
|
||||
$color = self::get_color( $color, $type );
|
||||
return $color;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Grabs the color index for each of five sample points of the image
|
||||
*
|
||||
* @param $image
|
||||
* @param $type can be 'index' or 'hex'
|
||||
* @return array() with color indices
|
||||
*
|
||||
*/
|
||||
function grab_points( $type = 'index' ) {
|
||||
$img = $this->image_obj;
|
||||
if ( ! $img )
|
||||
return false;
|
||||
|
||||
$height = imagesy( $img );
|
||||
$width = imagesx( $img );
|
||||
|
||||
// Sample five points in the image
|
||||
// Based on rule of thirds and center
|
||||
$topy = round( $height / 3 );
|
||||
$bottomy = round( ( $height / 3 ) * 2 );
|
||||
$leftx = round( $width / 3 );
|
||||
$rightx = round( ( $width / 3 ) * 2 );
|
||||
$centery = round( $height / 2 );
|
||||
$centerx = round( $width / 2 );
|
||||
|
||||
// Cast those colors into an array
|
||||
$points = array(
|
||||
imagecolorat( $img, $leftx, $topy ),
|
||||
imagecolorat( $img, $rightx, $topy ),
|
||||
imagecolorat( $img, $leftx, $bottomy ),
|
||||
imagecolorat( $img, $rightx, $bottomy ),
|
||||
imagecolorat( $img, $centerx, $centery ),
|
||||
);
|
||||
|
||||
if ( 'hex' == $type ) {
|
||||
foreach ( $points as $i => $p ) {
|
||||
$c = imagecolorsforindex( $img, $p );
|
||||
$points[ $i ] = self::get_color( array(
|
||||
'r' => $c['red'],
|
||||
'g' => $c['green'],
|
||||
'b' => $c['blue'],
|
||||
), 'hex' );
|
||||
}
|
||||
}
|
||||
|
||||
return $points;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Finds the average color of the image based on five sample points
|
||||
*
|
||||
* @param $image
|
||||
* @return array() with rgb color
|
||||
*
|
||||
*/
|
||||
function grab_color() {
|
||||
$img = $this->image_obj;
|
||||
if ( ! $img )
|
||||
return false;
|
||||
|
||||
$rgb = self::grab_points();
|
||||
|
||||
// Process the color points
|
||||
// Find the average representation
|
||||
foreach ( $rgb as $color ) {
|
||||
$index = imagecolorsforindex( $img, $color );
|
||||
$r[] = $index['red'];
|
||||
$g[] = $index['green'];
|
||||
$b[] = $index['blue'];
|
||||
|
||||
$red = round( array_sum( $r ) / 5 );
|
||||
$green = round( array_sum( $g ) / 5 );
|
||||
$blue = round( array_sum( $b ) / 5 );
|
||||
}
|
||||
|
||||
// The average color of the image as rgb array
|
||||
$color = array(
|
||||
'r' => $red,
|
||||
'g' => $green,
|
||||
'b' => $blue,
|
||||
);
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get a Color object using /lib class.color
|
||||
* Convert to appropriate type
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
function get_color( $color, $type ) {
|
||||
$c = new Jetpack_Color( $color, 'rgb' );
|
||||
$this->color = $c;
|
||||
|
||||
switch ( $type ) {
|
||||
case 'rgb' :
|
||||
$color = implode( $c->toRgbInt(), ',' );
|
||||
break;
|
||||
case 'hex' :
|
||||
$color = $c->toHex();
|
||||
break;
|
||||
case 'hsv' :
|
||||
$color = implode( $c->toHsvInt(), ',' );
|
||||
break;
|
||||
default:
|
||||
return $color = $c->toHex();
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Checks contrast against main color
|
||||
* Gives either black or white for using with opacity
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
function contrast() {
|
||||
if ( ! $this->color )
|
||||
return false;
|
||||
|
||||
$c = $this->color->getMaxContrastColor();
|
||||
return implode( $c->toRgbInt(), ',' );
|
||||
}
|
||||
|
||||
};
|
168
plugins/jetpack/_inc/lib/tracks/class.tracks-client.php
Normal file
168
plugins/jetpack/_inc/lib/tracks/class.tracks-client.php
Normal file
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Jetpack_Tracks_Client
|
||||
* @autounit nosara tracks-client
|
||||
*
|
||||
* Send Tracks events on behalf of a user
|
||||
*
|
||||
* Example Usage:
|
||||
```php
|
||||
require( dirname(__FILE__).'path/to/tracks/class.tracks-client' );
|
||||
|
||||
$result = Jetpack_Tracks_Client::record_event( array(
|
||||
'_en' => $event_name, // required
|
||||
'_ui' => $user_id, // required unless _ul is provided
|
||||
'_ul' => $user_login, // required unless _ui is provided
|
||||
|
||||
// Optional, but recommended
|
||||
'_ts' => $ts_in_ms, // Default: now
|
||||
'_via_ip' => $client_ip, // we use it for geo, etc.
|
||||
|
||||
// Possibly useful to set some context for the event
|
||||
'_via_ua' => $client_user_agent,
|
||||
'_via_url' => $client_url,
|
||||
'_via_ref' => $client_referrer,
|
||||
|
||||
// For user-targeted tests
|
||||
'abtest_name' => $abtest_name,
|
||||
'abtest_variation' => $abtest_variation,
|
||||
|
||||
// Your application-specific properties
|
||||
'custom_property' => $some_value,
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
// Handle the error in your app
|
||||
}
|
||||
```
|
||||
*/
|
||||
|
||||
require_once( dirname(__FILE__).'/class.tracks-client.php' );
|
||||
|
||||
class Jetpack_Tracks_Client {
|
||||
const PIXEL = 'http://pixel.wp.com/t.gif';
|
||||
const BROWSER_TYPE = 'php-agent';
|
||||
const USER_AGENT_SLUG = 'tracks-client';
|
||||
const VERSION = '0.3';
|
||||
|
||||
/**
|
||||
* record_event
|
||||
* @param mixed $event Event object to send to Tracks. An array will be cast to object. Required.
|
||||
* Properties are included directly in the pixel query string after light validation.
|
||||
* @return mixed True on success, WP_Error on failure
|
||||
*/
|
||||
static function record_event( $event ) {
|
||||
if ( ! $event instanceof Jetpack_Tracks_Event ) {
|
||||
$event = new Jetpack_Tracks_Event( $event );
|
||||
}
|
||||
if ( is_wp_error( $event ) ) {
|
||||
return $event;
|
||||
}
|
||||
|
||||
$pixel = $event->build_pixel_url( $event );
|
||||
|
||||
if ( ! $pixel ) {
|
||||
return new WP_Error( 'invalid_pixel', 'cannot generate tracks pixel for given input', 400 );
|
||||
}
|
||||
|
||||
return self::record_pixel( $pixel );
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously request the pixel
|
||||
*/
|
||||
static function record_pixel( $pixel ) {
|
||||
// Add the Request Timestamp and URL terminator just before the HTTP request.
|
||||
$pixel .= '&_rt=' . self::build_timestamp() . '&_=_';
|
||||
|
||||
$response = wp_remote_get( $pixel, array(
|
||||
'blocking' => true, // The default, but being explicit here :)
|
||||
'timeout' => 1,
|
||||
'redirection' => 2,
|
||||
'httpversion' => '1.1',
|
||||
'user-agent' => self::get_user_agent(),
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$code = isset( $response['response']['code'] ) ? $response['response']['code'] : 0;
|
||||
|
||||
if ( $code !== 200 ) {
|
||||
return new WP_Error( 'request_failed', 'Tracks pixel request failed', $code );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static function get_user_agent() {
|
||||
return Jetpack_Tracks_Client::USER_AGENT_SLUG . '-v' . Jetpack_Tracks_Client::VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an event and return its tracking URL
|
||||
* @deprecated Call the `build_pixel_url` method on a Jetpack_Tracks_Event object instead.
|
||||
* @param array $event Event keys and values
|
||||
* @return string URL of a tracking pixel
|
||||
*/
|
||||
static function build_pixel_url( $event ) {
|
||||
$_event = new Jetpack_Tracks_Event( $event );
|
||||
return $_event->build_pixel_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate input for a tracks event.
|
||||
* @deprecated Instantiate a Jetpack_Tracks_Event object instead
|
||||
* @param array $event Event keys and values
|
||||
* @return mixed Validated keys and values or WP_Error on failure
|
||||
*/
|
||||
private static function validate_and_sanitize( $event ) {
|
||||
$_event = new Jetpack_Tracks_Event( $event );
|
||||
if ( is_wp_error( $_event ) ) {
|
||||
return $_event;
|
||||
}
|
||||
return get_object_vars( $_event );
|
||||
}
|
||||
|
||||
// Milliseconds since 1970-01-01
|
||||
static function build_timestamp() {
|
||||
$ts = round( microtime( true ) * 1000 );
|
||||
return number_format( $ts, 0, '', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs the user's anon id from cookies, or generates and sets a new one
|
||||
*
|
||||
* @return string An anon id for the user
|
||||
*/
|
||||
static function get_anon_id() {
|
||||
static $anon_id = null;
|
||||
|
||||
if ( ! isset( $anon_id ) ) {
|
||||
|
||||
// Did the browser send us a cookie?
|
||||
if ( isset( $_COOKIE[ 'tk_ai' ] ) && preg_match( '#^[A-Za-z0-9+/=]{24}$#', $_COOKIE[ 'tk_ai' ] ) ) {
|
||||
$anon_id = $_COOKIE[ 'tk_ai' ];
|
||||
} else {
|
||||
|
||||
$binary = '';
|
||||
|
||||
// Generate a new anonId and try to save it in the browser's cookies
|
||||
// Note that base64-encoding an 18 character string generates a 24-character anon id
|
||||
for ( $i = 0; $i < 18; ++$i ) {
|
||||
$binary .= chr( mt_rand( 0, 255 ) );
|
||||
}
|
||||
|
||||
$anon_id = 'jetpack:' . base64_encode( $binary );
|
||||
|
||||
if ( ! headers_sent() ) {
|
||||
setcookie( 'tk_ai', $anon_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $anon_id;
|
||||
}
|
||||
}
|
149
plugins/jetpack/_inc/lib/tracks/class.tracks-event.php
Normal file
149
plugins/jetpack/_inc/lib/tracks/class.tracks-event.php
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @autounit nosara tracks-client
|
||||
*
|
||||
* Example Usage:
|
||||
```php
|
||||
require_once( dirname(__FILE__) . 'path/to/tracks/class.tracks-event' );
|
||||
|
||||
$event = new Jetpack_Tracks_Event( array(
|
||||
'_en' => $event_name, // required
|
||||
'_ui' => $user_id, // required unless _ul is provided
|
||||
'_ul' => $user_login, // required unless _ui is provided
|
||||
|
||||
// Optional, but recommended
|
||||
'_via_ip' => $client_ip, // for geo, etc.
|
||||
|
||||
// Possibly useful to set some context for the event
|
||||
'_via_ua' => $client_user_agent,
|
||||
'_via_url' => $client_url,
|
||||
'_via_ref' => $client_referrer,
|
||||
|
||||
// For user-targeted tests
|
||||
'abtest_name' => $abtest_name,
|
||||
'abtest_variation' => $abtest_variation,
|
||||
|
||||
// Your application-specific properties
|
||||
'custom_property' => $some_value,
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $event->error ) ) {
|
||||
// Handle the error in your app
|
||||
}
|
||||
|
||||
$bump_and_redirect_pixel = $event->build_signed_pixel_url();
|
||||
```
|
||||
*/
|
||||
|
||||
require_once( dirname(__FILE__) . '/class.tracks-client.php' );
|
||||
|
||||
class Jetpack_Tracks_Event {
|
||||
const EVENT_NAME_REGEX = '/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/';
|
||||
const PROP_NAME_REGEX = '/^[a-z_][a-z0-9_]*$/';
|
||||
public $error;
|
||||
|
||||
function __construct( $event ) {
|
||||
$_event = self::validate_and_sanitize( $event );
|
||||
if ( is_wp_error( $_event ) ) {
|
||||
$this->error = $_event;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( $_event as $key => $value ) {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
function record() {
|
||||
return Jetpack_Tracks_Client::record_event( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotate the event with all relevant info.
|
||||
* @param mixed $event Object or (flat) array
|
||||
* @return mixed The transformed event array or WP_Error on failure.
|
||||
*/
|
||||
static function validate_and_sanitize( $event ) {
|
||||
$event = (object) $event;
|
||||
|
||||
// Required
|
||||
if ( ! $event->_en ) {
|
||||
return new WP_Error( 'invalid_event', 'A valid event must be specified via `_en`', 400 );
|
||||
}
|
||||
|
||||
// delete non-routable addresses otherwise geoip will discard the record entirely
|
||||
if ( property_exists( $event, '_via_ip' ) && preg_match( '/^192\.168|^10\./', $event->_via_ip ) ) {
|
||||
unset($event->_via_ip);
|
||||
}
|
||||
|
||||
$validated = array(
|
||||
'browser_type' => Jetpack_Tracks_Client::BROWSER_TYPE,
|
||||
'_aua' => Jetpack_Tracks_Client::get_user_agent(),
|
||||
);
|
||||
|
||||
$_event = (object) array_merge( (array) $event, $validated );
|
||||
|
||||
// If you want to blacklist property names, do it here.
|
||||
|
||||
// Make sure we have an event timestamp.
|
||||
if ( ! isset( $_event->_ts ) ) {
|
||||
$_event->_ts = Jetpack_Tracks_Client::build_timestamp();
|
||||
}
|
||||
|
||||
return $_event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a pixel URL that will send a Tracks event when fired.
|
||||
* On error, returns an empty string ('').
|
||||
*
|
||||
* @return string A pixel URL or empty string ('') if there were invalid args.
|
||||
*/
|
||||
function build_pixel_url() {
|
||||
if ( $this->error ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$args = get_object_vars( $this );
|
||||
|
||||
// Request Timestamp and URL Terminator must be added just before the HTTP request or not at all.
|
||||
unset( $args['_rt'] );
|
||||
unset( $args['_'] );
|
||||
|
||||
$validated = self::validate_and_sanitize( $args );
|
||||
|
||||
if ( is_wp_error( $validated ) )
|
||||
return '';
|
||||
|
||||
return Jetpack_Tracks_Client::PIXEL . '?' . http_build_query( $validated );
|
||||
}
|
||||
|
||||
static function event_name_is_valid( $name ) {
|
||||
return preg_match( Jetpack_Tracks_Event::EVENT_NAME_REGEX, $name );
|
||||
}
|
||||
|
||||
static function prop_name_is_valid( $name ) {
|
||||
return preg_match( Jetpack_Tracks_Event::PROP_NAME_REGEX, $name );
|
||||
}
|
||||
|
||||
static function scrutinize_event_names( $event ) {
|
||||
if ( ! Jetpack_Tracks_Event::event_name_is_valid( $event->_en ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$whitelisted_key_names = array(
|
||||
'anonId',
|
||||
'Browser_Type',
|
||||
);
|
||||
|
||||
foreach ( array_keys( (array) $event ) as $key ) {
|
||||
if ( in_array( $key, $whitelisted_key_names ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( ! Jetpack_Tracks_Event::prop_name_is_valid( $key ) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
124
plugins/jetpack/_inc/lib/tracks/client.php
Normal file
124
plugins/jetpack/_inc/lib/tracks/client.php
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
/**
|
||||
* PHP Tracks Client
|
||||
* @autounit nosara tracks-client
|
||||
* Example Usage:
|
||||
*
|
||||
```php
|
||||
include( plugin_dir_path( __FILE__ ) . 'lib/tracks/client.php');
|
||||
$result = jetpack_tracks_record_event( $user, $event_name, $properties );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
// Handle the error in your app
|
||||
}
|
||||
```
|
||||
*/
|
||||
|
||||
// Load the client classes
|
||||
require_once( dirname(__FILE__) . '/class.tracks-event.php' );
|
||||
require_once( dirname(__FILE__) . '/class.tracks-client.php' );
|
||||
|
||||
// Now, let's export a sprinkling of syntactic sugar!
|
||||
|
||||
/**
|
||||
* Procedurally (vs. Object-oriented), track an event object (or flat array)
|
||||
* NOTE: Use this only when the simpler jetpack_tracks_record_event() function won't work for you.
|
||||
* @param \Jetpack_Tracks_Event $event The event object.
|
||||
* @return \Jetpack_Tracks_Event|\WP_Error
|
||||
*/
|
||||
function jetpack_tracks_record_event_raw( $event ) {
|
||||
return Jetpack_Tracks_Client::record_event( $event );
|
||||
}
|
||||
|
||||
/**
|
||||
* Procedurally build a Tracks Event Object.
|
||||
* NOTE: Use this only when the simpler jetpack_tracks_record_event() function won't work for you.
|
||||
* @param $identity WP_user object
|
||||
* @param string $event_name The name of the event
|
||||
* @param array $properties Custom properties to send with the event
|
||||
* @param int $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred
|
||||
* @return \Jetpack_Tracks_Event|\WP_Error
|
||||
*/
|
||||
function jetpack_tracks_build_event_obj( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) {
|
||||
|
||||
$identity = jetpack_tracks_get_identity( $user->ID );
|
||||
|
||||
$properties['user_lang'] = $user->get( 'WPLANG' );
|
||||
|
||||
$blog_details = array(
|
||||
'blog_lang' => isset( $properties['blog_lang'] ) ? $properties['blog_lang'] : get_bloginfo( 'language' )
|
||||
);
|
||||
|
||||
$timestamp = ( $event_timestamp_millis !== false ) ? $event_timestamp_millis : round( microtime( true ) * 1000 );
|
||||
$timestamp_string = is_string( $timestamp ) ? $timestamp : number_format( $timestamp, 0, '', '' );
|
||||
|
||||
return new Jetpack_Tracks_Event( array_merge( $blog_details, (array) $properties, $identity, array(
|
||||
'_en' => $event_name,
|
||||
'_ts' => $timestamp_string
|
||||
) ) );
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the identity to send to tracks.
|
||||
*
|
||||
* @param int $user_id The user id of the local user
|
||||
* @return array $identity
|
||||
*/
|
||||
function jetpack_tracks_get_identity( $user_id ) {
|
||||
|
||||
// Meta is set, and user is still connected. Use WPCOM ID
|
||||
$wpcom_id = get_user_meta( $user_id, 'jetpack_tracks_wpcom_id', true );
|
||||
if ( $wpcom_id && Jetpack::is_user_connected( $user_id ) ) {
|
||||
return array(
|
||||
'_ut' => 'wpcom:user_id',
|
||||
'_ui' => $wpcom_id
|
||||
);
|
||||
}
|
||||
|
||||
// User is connected, but no meta is set yet. Use WPCOM ID and set meta.
|
||||
if ( Jetpack::is_user_connected( $user_id ) ) {
|
||||
$wpcom_user_data = Jetpack::get_connected_user_data( $user_id );
|
||||
add_user_meta( $user_id, 'jetpack_tracks_wpcom_id', $wpcom_user_data['ID'], true );
|
||||
|
||||
return array(
|
||||
'_ut' => 'wpcom:user_id',
|
||||
'_ui' => $wpcom_user_data['ID']
|
||||
);
|
||||
}
|
||||
|
||||
// User isn't linked at all. Fall back to anonymous ID.
|
||||
$anon_id = get_user_meta( $user_id, 'jetpack_tracks_anon_id', true );
|
||||
if ( ! $anon_id ) {
|
||||
$anon_id = Jetpack_Tracks_Client::get_anon_id();
|
||||
add_user_meta( $user_id, 'jetpack_tracks_anon_id', $anon_id, false );
|
||||
}
|
||||
|
||||
if ( ! isset( $_COOKIE[ 'tk_ai' ] ) && ! headers_sent() ) {
|
||||
setcookie( 'tk_ai', $anon_id );
|
||||
}
|
||||
|
||||
return array(
|
||||
'_ut' => 'anon',
|
||||
'_ui' => $anon_id
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Record an event in Tracks - this is the preferred way to record events from PHP.
|
||||
*
|
||||
* @param mixed $identity username, user_id, or WP_user object
|
||||
* @param string $event_name The name of the event
|
||||
* @param array $properties Custom properties to send with the event
|
||||
* @param int $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred
|
||||
* @return bool true for success | \WP_Error if the event pixel could not be fired
|
||||
*/
|
||||
function jetpack_tracks_record_event( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) {
|
||||
$event_obj = jetpack_tracks_build_event_obj( $user, $event_name, $properties, $event_timestamp_millis );
|
||||
|
||||
if ( is_wp_error( $event_obj->error ) ) {
|
||||
return $event_obj->error;
|
||||
}
|
||||
|
||||
return $event_obj->record();
|
||||
}
|
49
plugins/jetpack/_inc/lib/tracks/tracks-ajax.js
Normal file
49
plugins/jetpack/_inc/lib/tracks/tracks-ajax.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
/* global jpTracksAJAX, jQuery */
|
||||
|
||||
(function( $, jpTracksAJAX ) {
|
||||
|
||||
$( document ).ready( function () {
|
||||
$( 'body' ).on( 'click', '.jptracks a, a.jptracks', function( event ) {
|
||||
|
||||
// We know that the jptracks element is either this, or its ancestor
|
||||
var $jptracks = $( this ).closest( '.jptracks' );
|
||||
|
||||
var data = {
|
||||
tracksNonce: jpTracksAJAX.jpTracksAJAX_nonce,
|
||||
action: 'jetpack_tracks',
|
||||
tracksEventType: 'click',
|
||||
tracksEventName: $jptracks.attr( 'data-jptracks-name' ),
|
||||
tracksEventProp: $jptracks.attr( 'data-jptracks-prop' ) || false
|
||||
};
|
||||
|
||||
// We need an event name at least
|
||||
if ( undefined === data.tracksEventName ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var url = $( this ).attr( 'href' );
|
||||
var target = $( this ).get( 0 ).target;
|
||||
if ( url && target && '_self' !== target ) {
|
||||
var newTabWindow = window.open( '', target );
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
$.ajax( {
|
||||
type: 'POST',
|
||||
url: jpTracksAJAX.ajaxurl,
|
||||
data: data
|
||||
} ).always( function() {
|
||||
// Continue on to whatever url they were trying to get to.
|
||||
if ( url ) {
|
||||
if ( newTabWindow ) {
|
||||
newTabWindow.location = url;
|
||||
return;
|
||||
}
|
||||
window.location = url;
|
||||
}
|
||||
} );
|
||||
});
|
||||
});
|
||||
|
||||
})( jQuery, jpTracksAJAX );
|
Reference in a new issue