inc/WPDLib/Util/Util.php
<?php
/**
* WPDLib\Util\Util class
*
* @package WPDLib
* @subpackage Util
* @author Felix Arntz <felix-arntz@leaves-and-love.net>
* @since 0.5.0
*/
namespace WPDLib\Util;
if ( ! defined( 'ABSPATH' ) ) {
die();
}
if ( ! class_exists( 'WPDLib\Util\Util' ) ) {
/**
* This class contains some utility functions.
*
* @since 0.5.0
*/
final class Util {
/**
* @since 0.5.0
* @var string Temporarily holds the property to sort by in the below methods.
*/
private static $sort_by = '';
/**
* Transforms a component object into its slug.
*
* This can be used in `array_map()` for example.
*
* @since 0.5.0
* @param WPDLib\Components\Base $component the component to transform
* @return string the component's slug
*/
public static function component_to_slug( $component ) {
return $component->slug;
}
/**
* Checks whether the current user can access a specific component.
*
* @since 0.5.0
* @param WPDLib\Components\Base $component the component to check access for
* @return bool whether the user can access the component
*/
public static function current_user_can( $component ) {
$cap = $component->capability;
if ( null === $cap || current_user_can( $cap ) ) {
return true;
}
return false;
}
/**
* Returns an array of post IDs and their titles.
*
* Those can be used to generate dropdown options for example.
*
* @since 0.5.0
* @param string|array $post_type the post type / post types to get posts for
* @return array the array of `$post_id => $post_title`
*/
public static function get_posts_options( $post_type = 'any' ) {
if ( ! is_string( $post_type ) && ! is_array( $post_type ) || empty( $post_type ) ) {
$post_type = 'any';
}
$posts = get_posts( array(
'posts_per_page' => -1,
'post_type' => $post_type,
'post_status' => 'publish',
'orderby' => 'post_title',
'order' => 'asc',
'fields' => 'ids',
) );
$results = array();
foreach ( $posts as $post_id ) {
$results[ $post_id ] = get_the_title( $post_id );
}
return $results;
}
/**
* Returns an array of term IDs and their names.
*
* Those can be used to generate dropdown options for example.
*
* @since 0.5.0
* @param string|array $taxonomy the taxonomy / taxonomies to get terms for
* @return array the array of `$term_id => $term_name`
*/
public static function get_terms_options( $taxonomy = 'any' ) {
if ( ! is_string( $taxonomy ) && ! is_array( $taxonomy ) || empty( $taxonomy ) || 'any' === $taxonomy ) {
$taxonomy = array();
}
return get_terms( $taxonomy, array(
'number' => 0,
'hide_empty' => false,
'orderby' => 'name',
'order' => 'asc',
'fields' => 'id=>name',
) );
}
/**
* Returns an array of user IDs and their names.
*
* Those can be used to generate dropdown options for example.
*
* @since 0.5.0
* @param string $role the role to get users for
* @return array the array of `$user_id => $user_display_name`
*/
public static function get_users_options( $role = 'any' ) {
if ( is_array( $role ) ) {
if ( count( $role ) > 0 ) {
$role = $role[0];
}
}
if ( ! is_string( $role ) || 'any' === $role ) {
$role = '';
}
$users = get_users( array(
'number' => 0,
'role' => $role,
'orderby' => 'display_name',
'order' => 'asc',
'fields' => array( 'ID', 'display_name' ),
) );
$results = array();
foreach ( $users as $user ) {
$results[ $user->ID ] = $user->display_name;
}
return $results;
}
/**
* Formats a numeric value with a specific unit, depending on how big the value is.
*
* You need to specify a $units array and a base value.
* The keys of the units in the $units array are used to exponentiate the base value.
* The $base_unit must be the unit that the $value is specified in (usually this would be the first unit in the array).
*
* Two examples of how to use the function:
*
* - `format_unit( 244, array( 'mm', 'cm', 'dm', 'm' ), 10, 'mm' );` will produce 2.44 dm
* - `format_unit( 1235, array( 'B', 'kB', 'MB', 'GB', 'TB' ), 1024, 'B' );` will produce 1.21 kB
*
* @param integer|float $value the value to format with a unit
* @param array $units the array of units in an ascending order
* @param integer|float $base the base value (for exponentiation)
* @param string $base_unit the base unit (optional, default is the first unit in the array)
* @param integer $decimals (number of decimals to display, default is 2)
* @return string the formatted value
*/
public static function format_unit( $value, $units, $base, $base_unit = '', $decimals = 2 ) {
$value = floatval( $value );
if ( empty( $base_unit ) ) {
$base_unit = $units[0];
}
if ( $base_unit != $units[0] ) {
$value *= pow( $base, array_search( $base_unit, $units ) );
}
for ( $i = count( $units ) - 1; $i >= 0; $i-- ) {
if ( $value > pow( $base, $i ) ) {
return number_format_i18n( $value / pow( $base, $i ), $decimals ) . ' ' . $units[ $i ];
} elseif ( 0 == $i ) {
return number_format_i18n( $value, $decimals ) . ' ' . $units[0];
}
}
return $value;
}
/**
* Checks whether the rest of a division of two numbers equals zero.
*
* This is a reliable implementation that checks the rest for both integers and floats.
* While the check for integers is trivial, floating point numbers work differently.
* This function works around that to get proper results.
*
* @internal
* @since 0.6.1
* @param integer|float $divident the divident
* @param integer|float $divisor the divisor
* @return boolean true if the rest of the division is zero, otherwise false
*/
public static function is_rest_zero( $divident, $divisor ) {
$divident = abs( $divident );
$divisor = abs( $divisor );
if ( is_int( $divident ) && is_int( $divisor ) ) {
// prevent division by zero
if ( 0 === $divisor ) {
return false;
}
return 0 === $divident % $divisor;
}
if ( 0.0 === $divisor ) {
return false;
}
$compare = round( $divident / $divisor ) * $divisor;
return strval( $divident ) === strval( $compare );
}
/**
* A flexible PHP modulo function.
*
* Modulo for integers works properly using the % operator, but for floating point numbers it is not as accurate.
* Using `fmod()` does not actually provide the expected results.
*
* This function correctly calculates the rest of a division for both integers and floats.
*
* If both arguments are integers, the function will return an integer.
* Otherwise it will return a float.
*
* Note: This function still only works in some cases.
* Therefore it has been deprecated. WPDLib now uses `WPDLib\Util\Util::is_rest_zero()`.
*
* @internal
* @since 0.6.0
* @deprecated 0.6.1
* @param integer|float $divident the divident
* @param integer|float $divisor the divisor
* @return integer|float the rest of the division (modulo)
*/
public static function mod( $divident, $divisor ) {
if ( is_int( $divident ) && is_int( $divisor ) ) {
// prevent division by zero
if ( 0 === $divisor ) {
return 0;
}
return $divident % $divisor;
}
$divident = floatval( $divident );
$divisor = floatval( $divisor );
// prevent division by zero
if ( 0.0 === $divisor ) {
return 0.0;
}
$i = round( $divident / $divisor );
return $divident - $i * $divisor;
}
/**
* Inserts an object into an array in a sorted manner.
*
* If no $sort_by parameter is provided, the arrays won't be sorted.
*
* @internal
* @since 0.5.0
* @param array $arr the array to insert the item into
* @param object $item the object to insert
* @param string $key the property in the object to use as array key (default is 'slug')
* @param string $sort_by the property in the object to sort by (default is none for no sort order)
* @return array the array containing the object
*/
public static function object_array_insert( $arr, $item, $key = 'slug', $sort_by = '' ) {
if ( empty( $sort_by ) ) {
$arr[ $item->$key ] = $item;
return $arr;
} elseif ( ! isset( $item->$sort_by ) || null === $item->$sort_by || 0 == count( $arr ) ) {
$arr[ $item->$key ] = $item;
return $arr;
}
return self::object_array_insert_sorted( $arr, $item, $sort_by, $key );
}
/**
* Merges multiple sorted arrays of objects into a single sorted array.
*
* If no $sort_by parameter is provided, the arrays won't be sorted.
*
* PHP's native (unstable) sort will be run on all the items that have the $sort_by property defined.
* This way the sorting is "almost" stable.
* This means that items where the $sort_by property is 'null' will be ordered in the way they were originally specified in.
*
* @internal
* @since 0.5.0
* @param array $arrs the arrays to merge into one array
* @param string $key the property in the objects to use as array key (default is 'slug')
* @param string $sort_by the property in the objects to sort by (default is none for no sort order)
* @return array the resulting array
*/
public static function object_array_merge( $arrs, $key = 'slug', $sort_by = '' ) {
if ( empty( $sort_by ) ) {
$result = $instance_counts = array();
foreach ( $arrs as $arr ) {
$result = self::object_array_merge_items( $arr, $result, $instance_counts, $key );
}
return $result;
}
return self::object_array_merge_sorted( $arrs, $sort_by, $key );
}
/**
* Sorts an array of objects with PHP's native unstable sort.
*
* @internal
* @since 0.5.0
* @param array $arr the array to sort
* @param string $sort_by the property in the objects to sort by
* @param bool $associative whether the array is associative
* @return array the sorted array
*/
public static function object_array_sort( $arr, $sort_by, $associative = false ) {
if ( ! empty( $sort_by ) ) {
self::$sort_by = $sort_by;
if ( $associative ) {
uasort( $arr, array( __CLASS__, 'sort_objects_callback' ) );
} else {
usort( $arr, array( __CLASS__, 'sort_objects_callback' ) );
}
self::$sort_by = '';
}
return $arr;
}
/**
* Integrates an object into an array of objects in a way that the array is properly sorted.
*
* @internal
* @since 0.5.0
* @param array $arr the array to integrate the item into
* @param object $item the object to integrate
* @param string $sort_by the property in the object to sort by
* @param string $key the property in the object to use as array key (default is 'slug')
* @return array the array containing the object
*/
private static function object_array_insert_sorted( $arr, $item, $sort_by, $key = 'slug' ) {
$new_arr = array();
$new_arr[ $item->$key ] = $item;
$split_key = 0;
foreach ( $arr as $c ) {
if ( null === $c->$sort_by || $c->$sort_by > $item->$sort_by ) {
break;
}
$split_key++;
}
if ( 0 == $split_key ) {
$arr = array_merge( $new_arr, $arr );
} else {
$begin_arr = array_slice( $arr, 0, $split_key );
$end_arr = array_slice( $arr, $split_key );
$arr = array_merge( $begin_arr, $new_arr, $end_arr );
}
return $arr;
}
/**
* Merges multiple sorted arrays of objects into a single sorted array.
*
* The function splits the arrays into two arrays, one where the objects have a valid $sort_by property, the other with objects without one.
* Those arrays are then merged together, the array with valid $sort_by properties first.
*
* @internal
* @since 0.5.0
* @param array $arrs the arrays to merge into one array
* @param string $sort_by the property in the objects to sort by
* @param string $key the property in the objects to use as array key (default is 'slug')
* @return array the resulting array
*/
private static function object_array_merge_sorted( $arrs, $sort_by, $key = 'slug' ) {
$result = $instance_counts = array();
list( $sortables, $nulls ) = self::object_array_merge_split( $arrs, $sort_by );
$result = self::object_array_merge_items( $sortables, $result, $instance_counts, $key );
$result = self::object_array_merge_items( $nulls, $result, $instance_counts, $key );
return $result;
}
/**
* Splits arrays into two big arrays.
*
* The first array will be a sorted array where the objects have a valid $sort_by property.
* The second array will be an array where the objects do not have a valid $sort_by property.
*
* @internal
* @since 0.5.0
* @param array $arrs the arrays to split
* @param string $sort_by the property in the objects to sort by
* @return array the resulting array
*/
private static function object_array_merge_split( $arrs, $sort_by ) {
$sortables = array();
$nulls = array();
foreach ( $arrs as $arr ) {
foreach ( $arr as $item ) {
if ( ! isset( $item->$sort_by ) || null === $item->$sort_by ) {
$nulls[] = $item;
} else {
$sortables[] = $item;
}
}
}
$sortables = self::object_array_sort( $sortables, $sort_by );
return array( $sortables, $nulls );
}
/**
* Merges objects of one array into another array.
*
* If an object's key property already exists in the array, the array key will be counted up so that nothing gets overwritten.
*
* @internal
* @since 0.5.0
* @param array $items the array of items to merge into $result
* @param array $result the array the $items should be merged into
* @param array $instance_counts an array of counts of how many items of a certain key exist in the target array
* @param string $key the property in the objects to use as array key (default is 'slug')
* @return array the resulting array
*/
private static function object_array_merge_items( $items, $result, &$instance_counts, $key = 'slug' ) {
foreach ( $items as $item ) {
if ( ! isset( $result[ $item->$key ] ) ) {
$result[ $item->$key ] = $item;
} else {
if ( ! isset( $instance_counts[ $item->$key ] ) ) {
$instance_counts[ $item->$key ] = 1;
}
$instance_counts[ $item->$key ]++;
$result[ $item->$key . '-' . $instance_counts[ $item->$key ] ] = $item;
}
}
return $result;
}
/**
* Callback function for PHP's native array sorting functionality.
*
* It will sort objects by the $sort_by property (temporarily stored in a class-wide helper variable).
*
* @internal
* @since 0.5.0
* @param object $a an object to compare
* @param object $b another object to compare
* @return integer -1 if the first item should be before the second, 1 if the second item should be before the first, 0 if they are equal
*/
private static function sort_objects_callback( $a, $b ) {
$sort_by = self::$sort_by;
if ( ( ! isset( $a->$sort_by ) || null === $a->$sort_by ) && ( ! isset( $b->$sort_by ) || null === $b->$sort_by ) ) {
return 0;
} elseif ( ! isset( $a->$sort_by ) || null === $a->$sort_by ) {
return 1;
} elseif ( ! isset( $b->$sort_by ) || null === $b->$sort_by ) {
return -1;
}
if ( $a->$sort_by == $b->$sort_by ) {
return 0;
}
return ( $a->$sort_by < $b->$sort_by ? -1 : 1 );
}
}
}