first commit

This commit is contained in:
Roman Pyrih
2023-07-24 08:30:51 +02:00
commit c2e100a763
7128 changed files with 1622619 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
<?php if (!defined('FW')) die('Forbidden');
/**
* Used in public callbacks to allow call only from known caller
*
* For e.g. Inside some class is created an instance of FW_Access_Key with unique key 'whatever',
* so nobody else can create another instance with same key, only that class owns that unique key.
* Some public callback (function or method) that wants to allow to be called only by that class,
* sets an requirement that some parameter should be an instance on FW_Access_Key and its key should be 'whatever'
*
* function my_function(FW_Access_Key $key, $another_parameter) {
* if ($key->get_key() !== 'whatever') {
* trigger_error('Call denied', E_USER_ERROR);
* }
*
* //...
* }
*/
final class FW_Access_Key
{
private static $created_keys = array();
private $key;
final public function get_key()
{
return $this->key;
}
/**
* @param string $unique_key unique
*/
final public function __construct($unique_key)
{
if (isset(self::$created_keys[$unique_key])) {
trigger_error('Key "'. $unique_key .'" already defined', E_USER_ERROR);
}
self::$created_keys[$unique_key] = true;
$this->key = $unique_key;
}
}

View File

@@ -0,0 +1,309 @@
<?php if (!defined('FW')) die('Forbidden');
/**
* Memory Cache
*
* Recommended usage example:
* try {
* $value = FW_Cache::get('some/key');
* } catch(FW_Cache_Not_Found_Exception $e) {
* $value = get_value_from_somewhere();
*
* FW_Cache::set('some/key', $value);
*
* // (!) after set, do not do this:
* $value = FW_Cache::get('some/key');
* // because there is no guaranty that FW_Cache::set('some/key', $value); succeeded
* // trust only your $value, cache can do clean-up right after set() and remove the value you tried to set
* }
*
* // use $value ...
*/
class FW_Cache
{
/**
* The actual cache
* @var array
*/
protected static $cache = array();
/**
* If the PHP will have less that this memory, the cache will try to delete parts from its array to free memory
*
* (1024 * 1024 = 1048576 = 1 Mb) * 10
*/
protected static $min_free_memory = 10485760;
/**
* A special value that is used to detect if value was found in cache
* We can't use null|false because these can be values set by user and we can't treat them as not existing values
*/
protected static $not_found_value;
/**
* The amount of times the data was already stored in the cache.
* @var int
* @since 2.4.17
*/
protected static $hits = 0;
/**
* Amount of times the cache did not have the value in cache.
* @var int
* @since 2.4.17
*/
protected static $misses = 0;
/**
* Amount of times the cache free was called.
* @var int
* @since 2.4.17
*/
protected static $freed = 0;
protected static function get_memory_limit()
{
$memory_limit = ini_get('memory_limit');
if ($memory_limit === '-1') { // This happens in WP CLI
return 256 * 1024 * 1024;
}
switch (substr($memory_limit, -1)) {
case 'M': return intval($memory_limit) * 1024 * 1024;
case 'K': return intval($memory_limit) * 1024;
case 'G': return intval($memory_limit) * 1024 * 1024 * 1024;
default: return intval($memory_limit) * 1024 * 1024;
}
}
protected static function memory_exceeded()
{
return memory_get_usage(false) >= self::get_memory_limit() - self::$min_free_memory;
// about memory_get_usage(false) http://stackoverflow.com/a/16239377/1794248
}
/**
* @internal
*/
public static function _init()
{
self::$not_found_value = new FW_Cache_Not_Found_Exception();
/**
* Listen often triggered hooks to clear the memory
* instead of tick function https://github.com/ThemeFuse/Unyson/issues/1197
* @since 2.4.17
*/
foreach (array(
'query' => true,
'plugins_loaded' => true,
'wp_get_object_terms' => true,
'created_term' => true,
'wp_upgrade' => true,
'added_option' => true,
'updated_option' => true,
'deleted_option' => true,
'wp_after_admin_bar_render' => true,
'http_response' => true,
'oembed_result' => true,
'customize_post_value_set' => true,
'customize_save_after' => true,
'customize_render_panel' => true,
'customize_render_control' => true,
'customize_render_section' => true,
'role_has_cap' => true,
'user_has_cap' => true,
'theme_page_templates' => true,
'pre_get_users' => true,
'request' => true,
'send_headers' => true,
'updated_usermeta' => true,
'added_usermeta' => true,
'image_memory_limit' => true,
'upload_dir' => true,
'wp_head' => true,
'wp_footer' => true,
'wp' => true,
'wp_init' => true,
'fw_init' => true,
'init' => true,
'updated_postmeta' => true,
'deleted_postmeta' => true,
'setted_transient' => true,
'registered_post_type' => true,
'wp_count_posts' => true,
'wp_count_attachments' => true,
'after_delete_post' => true,
'post_updated' => true,
'wp_insert_post' => true,
'deleted_post' => true,
'clean_post_cache' => true,
'wp_restore_post_revision' => true,
'wp_delete_post_revision' => true,
'get_term' => true,
'edited_term_taxonomies' => true,
'deleted_term_taxonomy' => true,
'edited_terms' => true,
'created_term' => true,
'clean_term_cache' => true,
'edited_term_taxonomy' => true,
'switch_theme' => true,
'wp_get_update_data' => true,
'clean_user_cache' => true,
'process_text_diff_html' => true,
) as $hook => $tmp) {
add_filter($hook, array(__CLASS__, 'free_memory'), 1);
}
/**
* Flush the cache when something major is changed (files or db values)
*/
foreach (array(
'switch_blog' => true,
'upgrader_post_install' => true,
'upgrader_process_complete' => true,
'switch_theme' => true,
) as $hook => $tmp) {
add_filter($hook, array(__CLASS__, 'clear'), 1);
}
}
/**
* This method does nothing @since 2.4.17
* but we can't delete it because it's public and maybe somebody is calling it
* @return bool
*/
public static function is_enabled()
{
return true;
}
/**
* @param mixed $dummy
* @return mixed
*/
public static function free_memory($dummy = null)
{
while (self::memory_exceeded() && !empty(self::$cache)) {
reset(self::$cache);
$key = key(self::$cache);
unset(self::$cache[$key]);
}
++self::$freed;
/**
* This method is used in add_filter() so to not break anything return filter value
*/
return $dummy;
}
/**
* @param $keys
* @param $value
* @param $keys_delimiter
*/
public static function set($keys, $value, $keys_delimiter = '/')
{
if (!self::is_enabled()) {
return;
}
self::free_memory();
fw_aks($keys, $value, self::$cache, $keys_delimiter);
self::free_memory();
}
/**
* Unset key from cache
* @param $keys
* @param $keys_delimiter
*/
public static function del($keys, $keys_delimiter = '/')
{
fw_aku($keys, self::$cache, $keys_delimiter);
self::free_memory();
}
/**
* @param $keys
* @param $keys_delimiter
* @return mixed
* @throws FW_Cache_Not_Found_Exception
*/
public static function get($keys, $keys_delimiter = '/')
{
$keys = (string)$keys;
$keys_arr = explode($keys_delimiter, $keys);
$key = $keys_arr;
$key = array_shift($key);
if ($key === '' || $key === null) {
trigger_error('First key must not be empty', E_USER_ERROR);
}
self::free_memory();
$value = fw_akg($keys, self::$cache, self::$not_found_value, $keys_delimiter);
self::free_memory();
if ($value === self::$not_found_value) {
++self::$misses;
throw new FW_Cache_Not_Found_Exception();
} else {
++self::$hits;
return $value;
}
}
/**
* Empty the cache
* @param mixed $dummy When method is used in add_filter()
* @return mixed
*/
public static function clear($dummy = null)
{
self::$cache = array();
/**
* This method is used in add_filter() so to not break anything return filter value
*/
return $dummy;
}
/**
* Debug information
* <?php add_action('admin_footer', function(){ FW_Cache::stats(); });
* @since 2.4.17
*/
public static function stats() {
echo '<div style="z-index: 10000; position: relative; background: #fff; padding: 15px;">';
echo '<p>';
echo '<strong>Cache Hits:</strong> '. self::$hits .'<br />';
echo '<strong>Cache Misses:</strong> '. self::$misses .'<br />';
echo '<strong>Cache Freed:</strong> '. self::$freed .'<br />';
echo '<strong>PHP Memory Peak Usage:</strong> '. fw_human_bytes(memory_get_peak_usage(false)) .'<br />';
echo '</p>';
echo '<ul>';
foreach (self::$cache as $group => $cache) {
echo "<li><strong>Group:</strong> $group - ( " . number_format( strlen( serialize( $cache ) ) / KB_IN_BYTES, 2 ) . 'k )</li>';
}
echo '</ul>';
echo '</div>';
}
}
class FW_Cache_Not_Found_Exception extends Exception {}
FW_Cache::_init();

View File

@@ -0,0 +1,132 @@
<?php if ( ! defined( 'FW' ) ) {
die( 'Forbidden' );
}
/**
* Class FW_Callback
*
* @since 2.6.14
*/
class FW_Callback {
/**
* @var $callback string|array
*/
private $callback;
/**
* @var array $args
*/
private $args;
/**
* @var bool
*/
private $cache;
/**
* @var string
*/
private $id;
/**
* FW_Callback constructor.
*
* @param string|array|Closure $callback Callback function
* @param array $args Callback arguments
* @param bool $cache Whenever you want to cache the function value after it's first call or not
* Recommend when the function call may require many resources or time (database requests) , or the value is small
* Not recommended using on very large values
*
*/
public function __construct( $callback, array $args = array(), $cache = true ) {
$this->callback = $callback;
$this->args = $args;
$this->cache = (bool) $cache;
}
/**
* Return callback function
* @return array|string
*/
public function get_callback() {
return $this->callback;
}
/**
* Return callback function arguments
* @return array
*/
public function get_args() {
return $this->args;
}
/**
* Execute callback function
* @return mixed
*/
public function execute() {
if ( $this->cache ) {
try {
return FW_Cache::get( $this->get_id() );
} catch ( FW_Cache_Not_Found_Exception $e ) {
FW_Cache::set(
$this->get_id(),
$value = $this->get_value()
);
return $value;
}
} else {
return $this->get_value();
}
}
/**
* Whenever you want to clear the cached value or the function
*/
public function clear_cache() {
FW_Cache::del( $this->get_id() );
}
/**
* Get raw callback value, ignoring the cache
* @return mixed
*/
protected function get_value() {
return call_user_func_array( $this->callback, $this->args );
}
protected function get_id() {
if ( ! is_string( $this->id ) ) {
//$this->id = 'fw-callback-' . md5( $this->serialize_callback() . serialize( $this->args ) );
//Disabled temporary for optimization reasons
//Maybe later will come with a better idea.
$this->id = uniqid( 'fw-callback-' );
}
return $this->id;
}
protected function serialize_callback() {
if ( is_string( $this->callback ) ) {
return $this->callback;
}
if ( is_object( $this->callback ) ) {
return spl_object_hash( $this->callback );
}
if ( is_array( $this->callback ) ) {
$callback = $this->callback;
if ( is_object( ( $first = array_shift( $callback ) ) ) ) {
return spl_object_hash( $first ) . serialize( $callback );
}
return serialize( $this->callback );
}
return uniqid();
}
}

View File

@@ -0,0 +1,330 @@
<?php if (!defined('FW')) die('Forbidden');
/**
* Lets you create easy functions for get/set database option values
* it will handle all clever logic with default values, multikeys and processing options fw-storage parameter
* @since 2.5.9
*/
abstract class FW_Db_Options_Model {
/**
* @return string Must not contain '/'
*/
abstract protected function get_id();
/**
* @param null|int|string $item_id
* @param array $extra_data
* @return mixed
*/
abstract protected function get_values($item_id, array $extra_data = array());
/**
* @param null|int|string $item_id
* @param mixed $values
* @param array $extra_data
* @return void
*/
abstract protected function set_values($item_id, $values, array $extra_data = array());
/**
* @param null|int|string $item_id
* @param array $extra_data
* @return array
*/
abstract protected function get_options($item_id, array $extra_data = array());
/**
* @param null|int|string $item_id
* @param array $extra_data
* @return array E.g. for post options {'post-id': $item_id}
* @see fw_db_option_storage_type()
*/
abstract protected function get_fw_storage_params($item_id, array $extra_data = array());
abstract protected function _init();
/**
* @param null|int|string $item_id
* @param null|string $option_id
* @param null|string $sub_keys
* @param mixed $old_value
* @param array $extra_data
*/
protected function _after_set($item_id, $option_id, $sub_keys, $old_value, array $extra_data = array()) {}
/**
* Get sub-key. For e.g. if each item must have a separate key or not.
* @param string $key
* @param null|int|string $item_id
* @param array $extra_data
* @return null|string
*/
protected function _get_cache_key($key, $item_id, array $extra_data = array()) {
return empty($item_id) ? null : $item_id;
}
/**
* @var array {'id': mixed}
*/
private static $instances = array();
/**
* @param string $id
* @return FW_Db_Options_Model
* @internal
*/
final public static function _get_instance($id) {
return self::$instances[$id];
}
/**
* @return string
* @since 2.6.7
*/
final public function get_main_cache_key() {
return 'fw-options-model:'. $this->get_id();
}
final public function __construct() {
if (isset(self::$instances[ $this->get_id() ])) {
trigger_error(__CLASS__ .' with id "'. $this->get_id() .'" was already defined', E_USER_ERROR);
} else {
self::$instances[ $this->get_id() ] = $this;
}
$this->_init();
}
private function get_cache_key($key, $item_id, array $extra_data = array()) {
$item_key = $this->_get_cache_key($key, $item_id, $extra_data);
return $this->get_main_cache_key() .'/'. $key . (empty($item_key) ? '' : '/'. $item_key);
}
/**
* @param null|int|string $item_id
* @param null|string $option_id
* @param mixed $default_value
* @param array $extra_data
* @return mixed
*/
final public function get( $item_id = null, $option_id = null, $default_value = null, array $extra_data = array() ) {
if ( is_preview() ) {
global $wp_query;
if ( $wp_query->queried_object && ( is_single( $item_id ) || is_page( $item_id ) ) ) {
$reset_get_rev = wp_get_post_revisions( $item_id );
$item_id = ( $rewisions = reset( $reset_get_rev ) ) && isset( $rewisions->ID ) ? $rewisions->ID : $item_id;
}
}
if ( empty( $option_id ) ) {
$sub_keys = null;
} else {
$option_id = explode( '/', $option_id ); // 'option_id/sub/keys'
$_option_id = array_shift( $option_id ); // 'option_id'
$sub_keys = empty( $option_id ) ? null : implode( '/', $option_id ); // 'sub/keys'
$option_id = $_option_id;
unset( $_option_id );
}
try {
// Cached because values are merged with extracted default values
$values = FW_Cache::get( $cache_key_values = $this->get_cache_key( 'values', $item_id, $extra_data ) );
} catch ( FW_Cache_Not_Found_Exception $e ) {
FW_Cache::set(
$cache_key_values,
$values = ( is_array( $values = $this->get_values( $item_id, $extra_data ) ) ? $values : array() )
);
}
/**
* If db value is not found and default value is provided
* return default value before the options file is loaded
*/
if ( ! is_null( $default_value ) ) {
if ( empty( $option_id ) ) {
if ( empty( $values )
&& (
is_array( $default_value )
||
fw_is_callback( is_array( $default_value ) )
)
) {
return fw_call( $default_value );
}
} else {
if ( is_null( $sub_keys ) ) {
if ( ! isset( $values[ $option_id ] ) ) {
return fw_call( $default_value );
}
} else {
if ( ! isset( $values[ $option_id ] ) || is_null( fw_akg( $sub_keys, $values[ $option_id ] ) ) ) {
return fw_call( $default_value );
}
}
}
}
try {
$options = FW_Cache::get( $cache_key = $this->get_cache_key( 'options', $item_id, $extra_data ) );
} catch ( FW_Cache_Not_Found_Exception $e ) {
FW_Cache::set( $cache_key, array() ); // prevent recursion
FW_Cache::set( $cache_key, $options = fw_extract_only_options( $this->get_options( $item_id, $extra_data ) ) );
}
if ( $options ) {
try {
FW_Cache::get(
// fixes https://github.com/ThemeFuse/Unyson/issues/2034
$cache_key_values_processed = $this->get_cache_key( 'values:processed', $item_id, $extra_data )
);
} catch ( FW_Cache_Not_Found_Exception $e ) {
/**
* Set cache value before processing options
* Fixes https://github.com/ThemeFuse/Unyson/issues/2034#issuecomment-248571149
*/
FW_Cache::set( $cache_key_values_processed, true );
// Complete missing db values with default values from options array
{
try {
$skip_types_process = FW_Cache::get( $cache_key = 'fw:options-default-values:skip-types' );
} catch ( FW_Cache_Not_Found_Exception $e ) {
FW_Cache::set(
$cache_key,
$skip_types_process = apply_filters( 'fw:options-default-values:skip-types', array(// 'type' => true
) )
);
}
foreach ( array_diff_key( fw_extract_only_options( $options ), $values ) as $id => $option ) {
$values[ $id ] = isset( $skip_types_process[ $option['type'] ] )
? (
isset( $option['value'] )
? $option['value']
: fw()->backend->option_type( $option['type'] )->get_defaults( 'value' )
)
: fw()->backend->option_type( $option['type'] )->get_value_from_input( $option, null );
}
}
foreach ( $options as $id => $option ) {
$values[ $id ] = fw()->backend->option_type( $option['type'] )->storage_load(
$id,
$option,
isset( $values[ $id ] ) ? $values[ $id ] : null,
$this->get_fw_storage_params( $item_id, $extra_data )
);
}
FW_Cache::set( $cache_key_values, $values );
}
}
if ( empty( $option_id ) ) {
return ( empty( $values ) && ( is_array( $default_value ) || fw_is_callback( $default_value ) ) )
? fw_call( $default_value )
: $values;
} else {
if ( is_null( $sub_keys ) ) {
return isset( $values[ $option_id ] )
? $values[ $option_id ]
: fw_call( $default_value );
} else {
return isset( $values[ $option_id ] )
? fw_akg( $sub_keys, $values[ $option_id ], $default_value )
: fw_call( $default_value );
}
}
}
final public function set( $item_id = null, $option_id = null, $value, array $extra_data = array() ) {
FW_Cache::del($cache_key_values = $this->get_cache_key('values', $item_id, $extra_data));
FW_Cache::del($cache_key_values_processed = $this->get_cache_key('values:processed', $item_id, $extra_data));
try {
$options = FW_Cache::get($cache_key = $this->get_cache_key('options', $item_id, $extra_data));
} catch (FW_Cache_Not_Found_Exception $e) {
FW_Cache::set($cache_key, array()); // prevent recursion
FW_Cache::set($cache_key, $options = fw_extract_only_options($this->get_options($item_id, $extra_data)));
}
$sub_keys = null;
if ($option_id) {
$option_id = explode('/', $option_id); // 'option_id/sub/keys'
$_option_id = array_shift($option_id); // 'option_id'
$sub_keys = empty($option_id) ? null : implode('/', $option_id); // 'sub/keys'
$option_id = $_option_id;
unset($_option_id);
$old_values = is_array($old_values = $this->get_values($item_id, $extra_data)) ? $old_values : array();
$old_value = isset($old_values[$option_id]) ? $old_values[$option_id] : null;
if ($sub_keys) { // update sub_key in old_value and use the entire value
$new_value = $old_value;
fw_aks($sub_keys, $value, $new_value);
$value = $new_value;
unset($new_value);
$old_value = fw_akg($sub_keys, $old_value);
}
if (isset($options[$option_id])) {
$value = fw()->backend->option_type($options[$option_id]['type'])->storage_save(
$option_id,
$options[$option_id],
$value,
$this->get_fw_storage_params($item_id, $extra_data)
);
}
$old_values[$option_id] = $value;
$this->set_values($item_id, $old_values, $extra_data);
unset($old_values);
} else {
$old_value = is_array($old_values = $this->get_values($item_id, $extra_data)) ? $old_values : array();
if ( ! is_array($value) ) {
$value = array();
}
if (empty($value)) {
// All options reset. Reset all fw-storage values too
// Fixes https://github.com/ThemeFuse/Unyson/issues/2179
foreach ($options as $_option_id => $_option) {
fw()->backend->option_type($options[$_option_id]['type'])->storage_save(
$_option_id,
$_option,
fw()->backend->option_type($options[$_option_id]['type'])->get_defaults('value'),
$this->get_fw_storage_params($item_id, $extra_data)
);
}
} else {
foreach ($value as $_option_id => $_option_value) {
if (isset($options[$_option_id])) {
$value[$_option_id] = fw()->backend->option_type($options[$_option_id]['type'])->storage_save(
$_option_id,
$options[$_option_id],
$_option_value,
$this->get_fw_storage_params($item_id, $extra_data)
);
}
}
}
$this->set_values($item_id, $value, $extra_data);
}
FW_Cache::del($cache_key_values); // fixes https://github.com/ThemeFuse/Unyson/issues/1538
FW_Cache::del($cache_key_values_processed);
$this->_after_set($item_id, $option_id, $sub_keys, $old_value, $extra_data);
}
}

View File

@@ -0,0 +1,124 @@
<?php
// original source: https://code.google.com/p/prado3/source/browse/trunk/framework/Util/TVar_dumper.php
/**
* TVar_dumper class.
*
* TVar_dumper is intended to replace the buggy PHP function var_dump and print_r.
* It can correctly identify the recursively referenced objects in a complex
* object structure. It also has a recursive depth control to avoid indefinite
* recursive display of some peculiar variables.
*
* TVar_dumper can be used as follows,
* <code>
* echo TVar_dumper::dump($var);
* </code>
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
* @package System.Util
* @since 3.0
*/
class FW_Dumper
{
private static $_objects;
private static $_output;
private static $_depth;
/**
* Converts a variable into a string representation.
* This method achieves the similar functionality as var_dump and print_r
* but is more robust when handling complex objects such as PRADO controls.
* @param mixed $var Variable to be dumped
* @param integer $depth Maximum depth that the dumper should go into the variable. Defaults to 10.
* @return string the string representation of the variable
*/
public static function dump($var, $depth=10)
{
self::reset_internals();
self::$_depth=$depth;
self::dump_internal($var,0);
$output = self::$_output;
self::reset_internals();
return $output;
}
private static function reset_internals()
{
self::$_output='';
self::$_objects=array();
self::$_depth=10;
}
private static function dump_internal($var,$level)
{
switch(gettype($var)) {
case 'boolean':
self::$_output.=$var?'true':'false';
break;
case 'integer':
self::$_output.="$var";
break;
case 'double':
self::$_output.="$var";
break;
case 'string':
self::$_output.="'$var'";
break;
case 'resource':
self::$_output.='{resource}';
break;
case 'NULL':
self::$_output.="null";
break;
case 'unknown type':
self::$_output.='{unknown}';
break;
case 'array':
if(self::$_depth<=$level)
self::$_output.='array(...)';
else if(empty($var))
self::$_output.='array()';
else
{
$keys=array_keys($var);
$spaces=str_repeat(' ',$level*4);
self::$_output.="array\n".$spaces.'(';
foreach($keys as $key)
{
self::$_output.="\n".$spaces." [$key] => ";
self::$_output.=self::dump_internal($var[$key],$level+1);
}
self::$_output.="\n".$spaces.')';
}
break;
case 'object':
if(($id=array_search($var,self::$_objects,true))!==false)
self::$_output.=get_class($var).'(...)';
else if(self::$_depth<=$level)
self::$_output.=get_class($var).'(...)';
else
{
$id=array_push(self::$_objects,$var);
$class_name=get_class($var);
$members=(array)$var;
$keys=array_keys($members);
$spaces=str_repeat(' ',$level*4);
self::$_output.="$class_name\n".$spaces.'(';
foreach($keys as $key)
{
$key_display=strtr(trim($key),array("\0"=>':'));
self::$_output.="\n".$spaces." [$key_display] => ";
self::$_output.=self::dump_internal($members[$key],$level+1);
}
self::$_output.="\n".$spaces.')';
}
break;
}
}
}

View File

@@ -0,0 +1,218 @@
<?php if (!defined('FW')) die('Forbidden');
/**
* Set flash messages
**
* Store messages in session (to not be lost between redirects) and remove them after they were shown to the user
*/
class FW_Flash_Messages
{
private static $available_types = array(
// 'type' => 'backend class/type' (only 2 backend types exists: error and updated)
'error' => 'error',
'warning' => 'update-nag',
'info' => 'updated',
'success' => 'updated'
);
private static $session_key = 'fw_flash_messages';
private static $frontend_printed = false;
private static function get_messages()
{
$messages = FW_Session::get(self::$session_key);
if (empty($messages) || !is_array($messages)) {
$messages = array_fill_keys(array_keys(self::$available_types), array());
}
return $messages;
}
private static function set_messages(array $messages)
{
FW_Session::set(self::$session_key, $messages);
}
/**
* Remove messages with ids from pending remove
*/
private static function process_pending_remove_ids()
{
$pending_remove = array();
foreach (self::get_messages() as $messages) {
if (empty($messages)) {
continue;
}
foreach ($messages as $message) {
if (empty($message['remove_ids'])) {
continue;
}
foreach ($message['remove_ids'] as $remove_id) {
$pending_remove[$remove_id] = true;
}
}
}
if (empty($pending_remove)) {
return;
}
$types = self::get_messages();
foreach ($types as $type => $messages) {
if (empty($messages)) {
continue;
}
foreach ($messages as $id => $message) {
if (isset($pending_remove[$id])) {
unset($types[$type][$id]);
}
}
}
self::set_messages( $types );
}
/**
* Add flash message
**
* @param string $id Unique id of the message
* @param string $message Message (can be html)
* @param string $type Type from $available_types
* @param array $removed_ids Remove flashes with this id(s)
* (For e.g. your message is success and some known error messages ids needs to be removed
* because they are not relevant anymore, your success message suppress/cancels them)
*/
public static function add($id, $message, $type = 'info', array $removed_ids = array())
{
if (!isset(self::$available_types[$type])) {
trigger_error(sprintf(__('Invalid flash message type: %s', 'tfuse'), $type), E_USER_WARNING);
$type = 'info';
}
$messages = self::get_messages();
$messages[$type][$id] = array(
'message' => $message,
'remove_ids' => $removed_ids,
);
self::set_messages($messages);
}
/**
* Use this method to print messages html in backend
* (used in action at the end of the file)
* @internal
*/
public static function _print_backend()
{
if (!session_id()) {
return; // fixes https://github.com/ThemeFuse/Unyson/issues/2219
}
self::process_pending_remove_ids();
$html = array_fill_keys(array_keys(self::$available_types), '');
$all_messages = self::get_messages();
foreach ($all_messages as $type => $messages) {
if (!empty($messages)) {
foreach ($messages as $id => $data) {
$html[$type] .=
'<div class="'. self::$available_types[$type] .' fw-flash-message">'.
'<p data-id="'. esc_attr($id) .'">'. $data['message'] .'</p>'.
'</div>';
unset($all_messages[$type][$id]);
}
$html[$type] = '<div class="fw-flash-type-'. $type .'">'. $html[$type] .'</div>';
}
}
unset($success, $error, $info);
self::set_messages($all_messages);
echo '<div class="fw-flash-messages">'. implode("\n\n", $html) .'</div>';
}
/**
* Use this method to print messages html in frontend
* @return bool If some html was printed or not
*/
public static function _print_frontend()
{
self::process_pending_remove_ids();
$html = array_fill_keys(array_keys(self::$available_types), '');
$all_messages = self::get_messages();
$messages_exists = false;
foreach ($all_messages as $type => $messages) {
if (empty($messages)) {
continue;
}
foreach ($messages as $id => $data) {
$html[$type] .= '<li class="fw-flash-message">'. nl2br($data['message']) .'</li>';
unset($all_messages[$type][$id]);
}
$html[$type] = '<ul class="fw-flash-type-'. $type .'">'. $html[$type] .'</ul>';
$messages_exists = true;
}
self::set_messages($all_messages);
self::$frontend_printed = true;
if ($messages_exists) {
echo '<div class="fw-flash-messages">';
echo implode("\n\n", $html);
echo '</div>';
return true;
} else {
return false;
}
}
public static function _frontend_printed()
{
return self::$frontend_printed;
}
public static function _get_messages($clear = false)
{
self::process_pending_remove_ids();
$messages = self::get_messages();
if ($clear) {
self::_clear();
}
return $messages;
}
/**
* Clear the FW_Flash_Messages messages
*
* @since 2.6.15
*/
public static function _clear() {
self::set_messages(array());
}
}

View File

@@ -0,0 +1,651 @@
<?php if ( ! defined( 'FW' ) ) {
die( 'Forbidden' );
}
/**
* Dynamic forms
*/
class FW_Form {
/**
* Store all form ids created with this class
* @var FW_Form[] {'form_id' => instance}
*
* @deprecated 2.6.15 Use FW_Form::get_forms()
*/
protected static $forms = array();
/**
* The id of the submitted form id
* @var string
*
* @deprecated 2.6.15
*/
protected static $submitted_id;
/**
* Hidden input name that stores the form id
* @var string
*
* @deprecated 2.6.15 Use self::get_form_id_name()
*/
protected static $id_input_name = 'fwf';
/**
* Form id
* @var string
*
* @deprecated 2.6.15 Use $this->get_id()
*/
protected $id;
/**
* Html attributes for <form> tag
* @var array
*
* @deprecated 2.6.15 Use $this->attr()
*/
protected $attr = array();
/**
* Found validation errors
* @var array
*/
protected $errors;
/**
* If the get_errors() method was called at leas once
* @var bool
*/
protected $errors_accessed = false;
/**
* If current request is the submit of this form
* @var bool
*
* @deprecated 2.6.15 Use $this->is_submitted()
*/
protected $is_submitted;
/**
* @var bool
*
* @deprecated 2.6.15
*/
protected $validate_and_save_called = false;
/**
* @var array
*
* @deprecated 2.6.15 Use $this->get_callbacks()
*/
protected $callbacks = array(
'render' => false,
'validate' => false,
'save' => false
);
private $request;
/**
* @param string $id Unique
* @param array $data (optional)
* array(
* 'render' => callback // The callback that will render the form's html
* 'validate' => callback // The callback that will validate user input
* 'save' => callback // The callback that will save successfully validated user input
* 'attr' => array() // Custom <form ...> attributes
* )
*/
public function __construct( $id, $data = array() ) {
try {
self::get_form( $id );
trigger_error( sprintf( __( 'Form with id "%s" was already defined', 'fw' ), $id ), E_USER_ERROR );
return;
} catch ( FW_Form_Not_Found_Exception $e ) {
}
$this
// set id
->set_id( $id )
// prepare callbacks
->set_callbacks( array(
'render' => fw_akg( 'render', $data, false ),
'validate' => fw_akg( 'validate', $data, false ),
'save' => fw_akg( 'save', $data, false ),
) )
// prepare attributes
->set_attr( (array) fw_akg( 'attr', $data, array() ) );
self::$forms[ $this->get_id() ] =& $this;
if ( did_action( 'wp_loaded' ) ) {
// in case if form instance was created after action
$this->_validate_and_save();
} else {
// attach to an action before 'send_headers' action, to be able to do redirects
add_action( 'wp_loaded', array( $this, '_validate_and_save' ), 101 );
}
add_action( 'fw_form_display:before_form', array( $this, '_action_fw_form_show_errors' ) );
}
/**
* @return string
*/
public function get_id() {
return $this->id;
}
/**
* Get validation errors
* @return array
*/
public function get_errors() {
if ( ! $this->validate_and_save_called ) {
fw_print( debug_backtrace() );
trigger_error( __METHOD__ . ' called before validation', E_USER_WARNING );
return array( '~' => true );
}
$this->errors_accessed = true;
return $this->_get_errors();
}
public function get_callbacks() {
return $this->callbacks;
}
public function errors_accessed() {
return $this->errors_accessed;
}
/**
* If current form was submitted, validate and save it
*
* Note: This callback can abort script execution if save does redirect
*
* @internal
*/
public function _validate_and_save() {
if ( ! self::is_form_submitted( $this->get_id() ) || $this->validate_and_save_called ) {
return;
}
$this->validate_and_save_called = true;
try {
$data = $this->submit( self::get_form_request( $this->get_id() ) );
if ( $this->_is_ajax() ) {
wp_send_json_success( array(
'save_data' => $data,
'flash_messages' => self::collect_flash_messages(),
) );
}
if ( ( $redirect = fw_akg( 'redirect', $data ) ) ) {
wp_redirect( $redirect );
exit;
}
} catch ( FW_Form_Invalid_Submission_Exception $e ) {
if ( $this->_is_ajax() ) {
wp_send_json_error( array(
'errors' => $this->get_errors(),
'flash_messages' => self::collect_flash_messages()
) );
}
}
}
/**
* @param FW_Form $form
*
* @internal
*
* You can overwrite it in case you do not need the errors to be shown for your form
*/
public function _action_fw_form_show_errors( $form ) {
if (
$form->get_id() != $this->get_id()
// errors in admin side are displayed by a script at the end of this file
|| is_admin()
|| ! $form->is_submitted()
|| $form->is_valid()
|| $form->errors_accessed()
) {
return;
}
/**
* Use this action to customize errors display in your theme
*/
do_action( 'fw_form_display_errors_frontend', $form );
$errors = $form->get_errors();
if ( empty( $errors ) ) {
return;
}
echo '<ul class="fw-form-errors">';
foreach ( $errors as $input_name => $error_message ) {
echo fw_html_tag(
'li',
array(
'data-input-name' => $input_name,
),
$error_message
);
}
echo '</ul>';
}
/**
* Get html attribute(s)
*
* @param null|string $name
*
* @return array|string
*/
public function attr( $name = null ) {
return $name !== null
? fw_akg( $name, $this->attr )
: $this->attr;
}
/**
* Render form's html
*
* @param array $data
*/
public function render( $data = array() ) {
$render_data = array(
'submit' => array(
'value' => __( 'Submit', 'fw' ),
/**
* you can set here custom submit button html
* and the 'value' parameter will not be used
*/
'html' => null,
),
'data' => $data,
'attr' => $this->attr(),
);
$html = '';
if ( $render_callback = fw_akg( 'render', $this->get_callbacks() ) ) {
ob_start();
$data = call_user_func_array( $render_callback, array( $render_data, $this ) );
$html = ob_get_clean();
if ( empty( $data ) ) {
// fix if returned wrong data from callback
$data = $render_data;
}
$render_data = $data;
}
do_action( 'fw_form_display:before_form', $this );
// display form errors in frontend
echo '<form ' . fw_attr_to_html( $render_data['attr'] ) . ' >';
do_action( 'fw_form_display:before', $this );
echo fw_html_tag( 'input',
array(
'type' => 'hidden',
'name' => self::get_form_id_name(),
'value' => $this->get_id(),
) );
wp_nonce_field( $this->get_nonce_action(), $this->get_nonce_name( $render_data ) );
if ( ! empty( $render_data['attr']['action'] ) && $render_data['attr']['method'] == 'get' ) {
/**
* Add query vars from the action attribute url to hidden inputs to not loose them
*/
parse_str( parse_url( $render_data['attr']['action'], PHP_URL_QUERY ), $query_vars );
if ( ! empty( $query_vars ) ) {
foreach ( $query_vars as $var_name => $var_value ) {
echo fw_html_tag( 'input',
array(
'type' => 'hidden',
'name' => $var_name,
'value' => $var_value,
) );
}
}
}
echo $html;
// In filter can be defined custom html for submit button
if ( isset( $render_data['submit']['html'] ) ) {
echo $render_data['submit']['html'];
} else {
echo fw_html_tag( 'input',
array(
'type' => 'submit',
'value' => $render_data['submit']['value']
) );
}
do_action( 'fw_form_display:after', $this );
echo '</form>';
do_action( 'fw_form_display:after_form', $this );
}
/**
* If now is a submit of this form
* @return bool
*/
public function is_submitted() {
return $this->request !== null;
}
/**
* @return bool
*/
public function is_valid() {
if ( ! $this->is_submitted() ) {
return null;
}
return count( $this->_get_errors() ) == 0;
}
/**
* @param array $request
*
* @throws FW_Form_Invalid_Submission_Exception
*
* @return mixed
*/
public function submit( array $request = array() ) {
$this->request = $request;
//Updated the deprecated member for those that extended the class and use it in code
$this->is_submitted = true;
$errors = $this->validate();
if ( ! empty( $errors ) ) {
throw new FW_Form_Invalid_Submission_Exception( $errors );
}
return $this->save();
}
protected function get_default_attr() {
return array(
'data-fw-form-id' => $this->get_id(),
'method' => 'post',
'action' => fw_current_url(),
'class' => 'fw_form_' . $this->get_id()
);
}
/**
* @param array $attr
*
* @return $this
*/
protected function set_attr( array $attr ) {
$this->attr = array_merge( $this->get_default_attr(), $attr );
return $this;
}
/**
* @param null $key
*
* @return array|mixed|null
*
* @since 2.6.15
*/
protected function get_request( $key = null ) {
return $key === null ? (array) $this->request : fw_akg( $key, $this->request );
}
/**
* @return string|null
*
* @since 2.6.15
*/
protected function get_nonce() {
return $this->get_request( $this->get_nonce_name() );
}
/**
* Returns forms errors without counting them as accessed
* @return array
*/
protected function _get_errors() {
return $this->errors;
}
/**
* @return string
*
* @since 2.6.15
*/
protected function get_nonce_action() {
return 'submit_fwf';
}
protected function check_nonce( $nonce ) {
return wp_verify_nonce( $nonce, $this->get_nonce_action() );
}
/**
* @return array
*/
protected function validate() {
/**
* Errors array {'input[name]' => 'Error message'}
*/
$errors = array();
if ( ! $this->check_nonce( $this->get_nonce() ) ) {
$errors[ $this->get_nonce_name() ] = esc_html__( 'Your session expired. Please refresh page and try again.', 'fw' );
}
/**
* Call validate callback
*
* Callback must 'manually' extract input values from $_POST (or $_GET)
*/
if ( ( $validate = fw_akg( 'validate', $this->get_callbacks() ) ) ) {
$errors = (array) call_user_func( $validate, $errors );
}
return $this->set_errors( $errors )->_get_errors();
}
/**
* @return array|mixed
*/
protected function save() {
$save_data = array(
// you can set here a url for redirect after save
'redirect' => null
);
/**
* Call save callback
*
* Callback must 'manually' extract input values from $_POST (or $_GET)
*/
if ( ( $save_callback = fw_akg( 'save', $this->get_callbacks() ) ) ) {
$data = call_user_func_array( $save_callback, array( $save_data ) );
if ( ! is_array( $data ) ) {
// fix if returned wrong data from callback
$data = $save_data;
}
$save_data = $data;
unset( $data );
}
return $save_data;
}
/**
* @return bool
*
* @deprecated 2.6.15
*/
protected function is_ajax() {
return self::_is_ajax();
}
protected function set_id( $id ) {
$this->id = $id;
return $this;
}
/**
* @param array $callbacks
*
* @return $this
*/
protected function set_callbacks( array $callbacks ) {
$this->callbacks = $callbacks;
return $this;
}
protected function set_errors( array $errors ) {
$this->errors = $errors;
return $this;
}
/**
* Some forms (like Forms extension frontend form) uses the same FW_Form instance for all sub-forms
* and they must be differentiated somehow.
* Fixes https://github.com/ThemeFuse/Unyson/issues/2033
*
* @param array $render_data
*
* @return string
* @since 2.6.6
*/
private function get_nonce_name( $render_data = array() ) {
return '_nonce_' . md5( $this->id . apply_filters( 'fw:form:nonce-name-data', '', $this, $render_data ) );
}
/**
* @return FW_Form[]
*
* @since 2.6.15
*/
public static function get_forms() {
return self::$forms;
}
/**
* @param $id
*
* @return FW_Form
* @throws FW_Form_Not_Found_Exception
*
* @since 2.6.15
*/
public static function get_form( $id ) {
if ( ! isset( self::$forms[ $id ] ) ) {
throw new FW_Form_Not_Found_Exception( "FW_Form $id was not defined" );
}
return self::$forms[ $id ];
}
/**
* Get submitted form instance (or false if no form is currently submitted)
* @return FW_Form|false
*/
public static function get_submitted() {
foreach ( self::get_forms() as $id => $form ) {
if ( self::is_form_submitted( $id ) ) {
return $form;
}
}
return false;
}
/**
* @return bool
*
* @since 2.6.15
*/
public static function _is_ajax() {
return ( defined( 'DOING_AJAX' ) && DOING_AJAX )
||
strtolower( fw_akg( 'HTTP_X_REQUESTED_WITH', $_SERVER ) ) == 'xmlhttprequest';
}
public static function get_form_request( $id ) {
if ( FW_Request::POST( self::get_form_id_name() ) == $id ) {
return FW_Request::POST();
}
if ( FW_Request::GET( self::get_form_id_name() ) == $id ) {
return FW_Request::GET();
}
return null;
}
/**
* @return string
*
* @since 2.6.15
*/
protected static function get_form_id_name() {
return 'fwf';
}
/**
* @param $id
*
* @return bool
*
* @since 2.6.15
*/
protected static function is_form_submitted( $id ) {
return self::get_form_request( $id ) !== null;
}
private static function collect_flash_messages() {
$flash_messages = array();
foreach ( FW_Flash_Messages::_get_messages( true ) as $type => $messages ) {
$flash_messages[ $type ] = array();
foreach ( $messages as $id => $message_data ) {
$flash_messages[ $type ][ $id ] = $message_data['message'];
}
}
return $flash_messages;
}
}

View File

@@ -0,0 +1,90 @@
<?php if (!defined('FW')) die('Forbidden');
/**
* WordPress automatically adds slashes to:
* $_REQUEST
* $_POST
* $_GET
* $_COOKIE
*
* For e.g.
*
* If value is simple, get value directly:
* $foo = isset($_GET['bar']) && $_GET['bar'] == 'yes';
*
* If value can contain some user input and can have quotes or json from some option, then use this helper:
* $foo = json_decode(FW_Request::POST('bar')); // json_decode($_POST('bar')) will not work if json will contain quotes
*
* You can test that problem.
* Add somewhere this code:
fw_print(array(
$_GET['test'],
json_decode($_GET['test']),
FW_Request::GET('test'),
json_decode(FW_Request::GET('test'))
));
* and access: http://your-site.com/?test={'a':1}
*/
class FW_Request
{
protected static function prepare_key($key)
{
return (get_magic_quotes_gpc() && is_string($key) ? addslashes($key) : $key);
}
protected static function get_set_key($multikey = null, $set_value = null, &$value)
{
$multikey = self::prepare_key($multikey);
if ($set_value === null) { // get
return fw_stripslashes_deep_keys($multikey === null ? $value : fw_akg($multikey, $value));
} else { // set
fw_aks($multikey, fw_addslashes_deep_keys($set_value), $value);
}
return '';
}
public static function GET($multikey = null, $default_value = null)
{
return fw_stripslashes_deep_keys(
$multikey === null
? $_GET
: fw_akg($multikey, $_GET, $default_value)
);
}
public static function POST($multikey = null, $default_value = null)
{
return fw_stripslashes_deep_keys(
$multikey === null
? $_POST
: fw_akg($multikey, $_POST, $default_value)
);
}
public static function COOKIE($multikey = null, $set_value = null, $expire = 0, $path = null)
{
if ($set_value !== null) {
// transforms a string ( key1/key2/key3 => key1][key2][key3] )
$multikey = str_replace('/', '][', $multikey) . ']';
// removes the first closed square bracket ( key1][key2][key3] => key1[key2][key3] )
$multikey = preg_replace('/\]/', '', $multikey, 1);
return setcookie($multikey, $set_value, $expire, $path);
} else {
return self::get_set_key($multikey, $set_value, $_COOKIE);
}
}
public static function REQUEST($multikey = null, $default_value = null)
{
return fw_stripslashes_deep_keys(
$multikey === null
? $_REQUEST
: fw_akg($multikey, $_REQUEST, $default_value)
);
}
}

View File

@@ -0,0 +1,158 @@
<?php if ( ! defined( 'FW' ) ) {
die( 'Forbidden' );
}
if ( ! class_exists( 'FW_Resize' ) ) {
class FW_Resize {
/**
* The singleton instance
*/
static private $instance = null;
/**
* No initialization allowed
*/
private function __construct() {
}
/**
* No cloning allowed
*/
private function __clone() {
}
static public function getInstance() {
if ( self::$instance == null ) {
self::$instance = new self;
}
return self::$instance;
}
private function get_attachment_info( $attachment ) {
$row = $this->get_attachment( $attachment );
$path = get_attached_file( $row['ID'] );
return ( ! isset( $row ) || ! $path ) ? false : array(
'id' => intval( $row['ID'] ),
'path' => $path,
'url' => is_ssl() ? preg_replace( "/^http:/i", "https:", $row['guid'] ) : $row['guid']
);
}
private function get_attachment( $attachment ) {
/**
* @var WPDB $wpdb
*/
global $wpdb;
if ( is_numeric( $attachment ) ) {
return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID=%d LIMIT 1", $attachment ), ARRAY_A );
} else {
$attachment = str_replace( array( 'http:', 'https:' ), '', $attachment );
return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE guid LIKE %s LIMIT 1", '%' . $wpdb->esc_like($attachment) ), ARRAY_A );
}
}
public function process( $attachment, $width = false, $height = false, $crop = false ) {
$attachment_info = $this->get_attachment_info( $attachment );
if ( ! $attachment_info ) {
return new WP_Error( 'invalid_attachment', 'Invalid Attachment', $attachment );
}
$file_path = $attachment_info['path'];
$info = pathinfo( $file_path );
$dir = $info['dirname'];
$ext = ( isset( $info['extension'] ) ) ? $info['extension'] : 'jpg';
$name = wp_basename( $file_path, ".$ext" );
$name = preg_replace( '/(.+)(\-\d+x\d+)$/', '$1', $name );
{
if ( ! $width || ! $height ) {
$editor = wp_get_image_editor( $file_path );
if (is_wp_error($editor)) {
return $editor;
}
$size = $editor->get_size();
$orig_width = $size['width'];
$orig_height = $size['height'];
if ( ! $height && $width ) {
$height = round( ( $orig_height * $width ) / $orig_width );
} elseif ( ! $width && $height ) {
$width = round( ( $orig_width * $height ) / $orig_height );
} else {
return $attachment;
}
}
}
// Suffix applied to filename
$suffix = "{$width}x{$height}";
// Get the destination file name
$destination_file_name = "{$dir}/{$name}-{$suffix}.{$ext}";
// No need to resize & create a new image if it already exists
if ( ! file_exists( $destination_file_name ) ) {
//Image Resize
$editor = (isset($editor)) ? $editor : wp_get_image_editor( $file_path );
if ( is_wp_error( $editor ) ) {
return new WP_Error( 'wp_image_editor', 'WP Image editor can\'t resize this attachment', $attachment );
}
// Get the original image size
$size = $editor->get_size();
$orig_width = $size['width'];
$orig_height = $size['height'];
$src_x = $src_y = 0;
$src_w = $orig_width;
$src_h = $orig_height;
if ( $crop ) {
$cmp_x = $orig_width / $width;
$cmp_y = $orig_height / $height;
// Calculate x or y coordinate, and width or height of source
if ( $cmp_x > $cmp_y ) {
$src_w = round( $orig_width / $cmp_x * $cmp_y );
$src_x = round( ( $orig_width - ( $orig_width / $cmp_x * $cmp_y ) ) / 2 );
} else if ( $cmp_y > $cmp_x ) {
$src_h = round( $orig_height / $cmp_y * $cmp_x );
$src_y = round( ( $orig_height - ( $orig_height / $cmp_y * $cmp_x ) ) / 2 );
}
}
$editor->crop( $src_x, $src_y, $src_w, $src_h, $width, $height );
$saved = $editor->save( $destination_file_name );
$images = wp_get_attachment_metadata( $attachment_info['id'] );
if ( ! empty( $images['resizes'] ) && is_array( $images['resizes'] ) ) {
foreach ( $images['resizes'] as $image_size => $image_path ) {
$images['resizes'][ $image_size ] = addslashes( $image_path );
}
}
$uploads_dir = wp_upload_dir();
$images['resizes'][ $suffix ] = $uploads_dir['subdir'] . '/' . $saved['file'];
wp_update_attachment_metadata( $attachment_info['id'], $images );
}
return array(
'id' => $attachment_info['id'],
'src' => str_replace( basename( $attachment_info['url'] ), basename( $destination_file_name ), $attachment_info['url'] )
);
}
}
}

View File

@@ -0,0 +1,38 @@
<?php if ( ! defined( 'FW' ) ) {
die( 'Forbidden' );
}
/**
* Work with $_SESSION
*
* Advantages: Do not session_start() on every refresh, but only when it is accessed
*/
class FW_Session {
private static function start_session() {
if ( apply_filters( 'fw_use_sessions', true ) && ! session_id() ) {
session_start();
}
}
public static function get( $key, $default_value = null ) {
if ( ! apply_filters( 'fw_use_sessions', true ) ) {
return array();
}
self::start_session();
return fw_akg( $key, $_SESSION, $default_value );
}
public static function set( $key, $value ) {
self::start_session();
fw_aks( $key, $value, $_SESSION );
}
public static function del( $key ) {
self::start_session();
fw_aku( $key, $_SESSION );
}
}

View File

@@ -0,0 +1,364 @@
<?php if (!defined('FW')) die('Forbidden');
/**
* Helps you create settings forms
* @since 2.6.9
*/
abstract class FW_Settings_Form {
/**
* @return array
*/
abstract public function get_options();
/**
* @return array
*/
abstract public function get_values();
/**
* @param array|callable $values
* @return $this
*/
abstract public function set_values($values);
/**
* Make sure all instances have unique id
* @var array
*/
private static $ids = array();
/**
* @var string
*/
private $id;
/**
* @var bool
*/
private $is_side_tabs = false;
/**
* @var bool
*/
private $is_ajax_submit = false;
/**
* @var FW_Form
*/
private $fw_form;
/**
* Translated text ( initialized in __construct() )
* @var array
*/
private $strings;
private static $input_name_reset = '_fw_reset_options';
private static $input_name_save = '_fw_save_options';
final public function __construct($id) {
if (isset(self::$ids[$id])) {
trigger_error(__CLASS__ .' with id "'. $id .'" was already defined', E_USER_ERROR);
} else {
self::$ids[$id] = true;
}
$this->id = $id;
$this->fw_form = new FW_Form('fw-settings-form:'. $this->get_id(), array(
'render' => array($this, '_form_render'),
'validate' => array($this, '_form_validate'),
'save' => array($this, '_form_save'),
));
$this->strings = array(
'title' => __('Settings', 'fw'),
'save_button' => __('Save Changes', 'fw'),
'reset_button' => __('Reset Options', 'fw'),
'reset_warning' => __("Click OK to reset.\nAll settings will be lost and replaced with default settings!", 'fw'),
);
$this->_init();
}
protected function _init() {}
/**
* @return string
*/
final public function get_id() {
return $this->id;
}
/**
* @return bool
*/
final public function get_is_ajax_submit() {
return $this->is_ajax_submit;
}
/**
* In order for this to work, you must call $this->enqueue_static() on `admin_enqueue_scripts` action
* @param bool $is_ajax_submit
* @return $this
*/
final public function set_is_ajax_submit($is_ajax_submit) {
$this->is_ajax_submit = (bool)$is_ajax_submit;
return $this;
}
/**
* @return bool
*/
final public function get_is_side_tabs() {
return $this->is_side_tabs;
}
/**
* @param bool $is_side_tabs
* @return $this
*/
final public function set_is_side_tabs($is_side_tabs) {
$this->is_side_tabs = (bool)$is_side_tabs;
return $this;
}
/**
* @param string $id
* @return string
*/
final public function get_string($id) {
return isset($this->strings[$id]) ? $this->strings[$id] : null;
}
/**
* @param string $id
* @param string $value
* @return bool
*/
final public function set_string($id, $value) {
if (isset($this->strings[$id])) {
$this->strings[$id] = $value;
}
return $this;
}
public function form_capability() {
return 'manage_options';
}
public function enqueue_static() {
fw()->backend->enqueue_options_static($this->get_options());
if ($this->get_is_ajax_submit()) {
wp_enqueue_script('fw-form-helpers');
}
}
final public function render() {
echo '<div class="wrap">';
if ( $this->get_is_side_tabs() ) {
// this is needed for flash messages (admin notices) to be displayed properly
echo '<h2 class="fw-hidden"></h2>';
} else {
echo '<h2>'. esc_html( $this->get_string('title') ) .'</h2><br/>';
}
$this->fw_form->render();
echo '</div>';
{
remove_action( // In case render is called multiple times
'admin_print_footer_scripts',
array($this, '_action_admin_print_footer_scripts')
);
add_action(
'admin_print_footer_scripts',
array($this, '_action_admin_print_footer_scripts'),
20
);
}
}
/**
* Previously the functionality from this class was hardcoded in fw()->backend for Theme Settings
* and there were hooks that developers use now, so we should use old hooks for Theme Settings form
* Backwards Compatibility
* @return bool
*/
private function is_theme_settings() {
return $this->get_id() === 'theme-settings';
}
/**
* @param array $data
* @return array
* @internal
*/
public function _form_render($data) {
$options = $this->get_options();
if ( empty( $options ) ) {
echo '<p><em>', esc_html__('No options to display.', 'fw'), '</em></p>';
return $data;
}
if ($this->is_theme_settings()) {
do_action('fw_settings_form_render', array(
'ajax_submit' => $this->get_is_ajax_submit(),
'side_tabs' => $this->get_is_side_tabs()
));
{
$texts = apply_filters('fw_settings_form_texts', array(
'save_button' => __('Save Changes', 'fw'),
'reset_button' => __('Reset Options', 'fw'),
));
$this->set_string('save_button', $texts['save_button']);
$this->set_string('reset_button', $texts['reset_button']);
}
}
{
$data['attr']['class'] = 'fw-settings-form';
if ( $this->get_is_side_tabs() ) {
$data['attr']['class'] .= ' fw-backend-side-tabs';
}
}
$data['submit']['html'] = '<!-- -->'; // it's generated in view
fw_render_view( fw_get_framework_directory( '/views/backend-settings-form.php' ), array(
'form' => $this,
'values' => (
($values = FW_Request::POST( fw()->backend->get_options_name_attr_prefix() ))
// This is form submit, extract values from $_POST
? ($values = fw_get_options_values_from_input( $options, $values ))
// Use saved values
: ($values = $this->get_values())
),
'is_theme_settings' => $this->is_theme_settings(),
'input_name_reset' => self::$input_name_reset,
'input_name_save' => self::$input_name_save,
'js_form_selector' => 'form[data-fw-form-id="'. esc_js($this->fw_form->get_id()) .'"]',
), false );
return $data;
}
/**
* @param array $errors
* @return array
* @internal
*/
public function _form_validate( $errors ) {
if ( ! current_user_can($this->form_capability()) ) {
$errors['_no_permission'] = __( 'You have no permissions to change settings options', 'fw' );
}
return $errors;
}
/**
* @param array $data
* @return array
* @internal
*/
public function _form_save( $data ) {
$flash_id = 'fw-settings-form:save:'. $this->get_id();
$old_values = (array)$this->get_values();
if ( ! empty( $_POST[ self::$input_name_reset ] ) ) { // The "Reset" button was pressed
/**
* Some values that don't relate to design, like API credentials, are useful to not be wiped out.
*
* Usage:
*
* add_filter('fw_settings_form_reset:values', '_filter_add_persisted_option', 10, 2);
* function _filter_add_persisted_option ($current_persisted, $old_values) {
* $value_to_persist = fw_akg('my/multi/key', $old_values);
* fw_aks('my/multi/key', $value_to_persist, $current_persisted);
*
* return $current_persisted;
* }
*/
$new_values = $this->is_theme_settings()
? apply_filters( 'fw_settings_form_reset:values', array(), $old_values )
: apply_filters( 'fw:settings-form:' . $this->get_id() . ':reset:values', array(), $old_values );
$this->set_values( $new_values );
FW_Flash_Messages::add(
$flash_id,
__( 'The options were successfully reset', 'fw' ),
'success'
);
if ( $this->is_theme_settings() ) {
do_action( 'fw_settings_form_reset', $old_values, $new_values );
} else {
do_action( 'fw:settings-form:' . $this->get_id() . ':reset', $old_values, $new_values );
}
} else { // The "Save" button was pressed
$new_values = fw_get_options_values_from_input( $this->get_options() );
$this->set_values( $new_values );
FW_Flash_Messages::add(
$flash_id,
__( 'The options were successfully saved', 'fw' ),
'success'
);
if ($this->is_theme_settings()) {
do_action('fw_settings_form_saved', $old_values, $new_values);
} else {
do_action('fw:settings-form:'. $this->get_id() .':saved', $old_values, $new_values);
}
}
$data['redirect'] = fw_current_url();
return $data;
}
/**
* @internal
*/
public function _action_admin_print_footer_scripts() {
?>
<script type="text/javascript">
(function ($) {
var fwLoadingId = 'fw-settings-form:<?php echo esc_js($this->get_id()); ?>';
<?php if (wp_script_is('fw-option-types')): ?>
// there are options on the page. show loading now and hide it after the options were initialized
{
fw.loading.show(fwLoadingId);
fwEvents.one('fw:options:init', function (data) {
fw.loading.hide(fwLoadingId);
});
}
<?php endif; ?>
$(function ($) {
$(document.body).on({
'fw:settings-form:before-html-reset': function () {
fw.loading.show(fwLoadingId);
},
'fw:settings-form:reset': function () {
fw.loading.hide(fwLoadingId);
}
});
});
})(jQuery);
</script>
<?php
}
}

View File

@@ -0,0 +1,378 @@
<?php if (!defined('FW')) die('Forbidden');
class FW_WP_Filesystem
{
/**
* Request WP Filesystem access
* @param string $context
* @param string $url
* @param array $extra_fields
* @return null|bool // todo: Create a new method that will return WP_Error with message on failure
* null - if has no access and the input credentials form was displayed
* false - if user submitted wrong credentials
* true - if we have filesystem access
*/
final public static function request_access($context = null, $url = null, $extra_fields = array())
{
/** @var WP_Filesystem_Base $wp_filesystem */
global $wp_filesystem;
if ($wp_filesystem) {
if (
is_object($wp_filesystem)
&&
!(is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())
) {
return true; // already initialized
}
}
if ( empty( $url ) ) {
$url = fw_current_url();
}
if ( get_filesystem_method() === 'direct' ) {
// in case if direct access is available
/* you can safely run request_filesystem_credentials() without any issues and don't need to worry about passing in a URL */
$creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, null );
/* initialize the API */
if ( ! WP_Filesystem( $creds ) ) {
/* any problems and we exit */
trigger_error( __( 'Cannot connect to Filesystem directly', 'fw' ), E_USER_WARNING );
return false;
}
} else {
$creds = request_filesystem_credentials( $url, '', false, $context, $extra_fields );
if ( ! $creds ) {
// the form was printed to the user
return null;
}
/* initialize the API */
if ( ! WP_Filesystem( $creds ) ) {
/* any problems and we exit */
request_filesystem_credentials( $url, '', true, $context, $extra_fields ); // the third parameter is true to show error to the user
return false;
}
}
if (
! is_object($wp_filesystem)
||
(is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code())
) {
return false;
}
if (
$wp_filesystem->abspath()
&&
$wp_filesystem->wp_content_dir()
&&
$wp_filesystem->wp_plugins_dir()
&&
$wp_filesystem->wp_themes_dir()
&&
$wp_filesystem->find_folder($context)
) {
return true;
} else {
return false;
}
}
/**
* @return array {base_dir_real_path => base_dir_wp_filesystem_path}
*/
public static function get_base_dirs_map()
{
/** @var WP_Filesystem_Base $wp_filesystem */
global $wp_filesystem;
if (!$wp_filesystem) {
trigger_error('Filesystem is not available', E_USER_ERROR);
} elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
trigger_error('Filesystem: '. $wp_filesystem->errors->get_error_message(), E_USER_ERROR);
}
try {
$cache_key = 'fw_wp_filesystem/base_dirs_map';
return FW_Cache::get($cache_key);
} catch (FW_Cache_Not_Found_Exception $e) {
// code from $wp_filesystem->wp_themes_dir()
{
$themes_dir = get_theme_root();
// Account for relative theme roots
if ( '/themes' == $themes_dir || ! is_dir( $themes_dir ) ) {
$themes_dir = WP_CONTENT_DIR . $themes_dir;
}
}
$dirs = array(
fw_fix_path(ABSPATH) => fw_fix_path($wp_filesystem->abspath()),
fw_fix_path(WP_CONTENT_DIR) => fw_fix_path($wp_filesystem->wp_content_dir()),
fw_fix_path(WP_PLUGIN_DIR) => fw_fix_path($wp_filesystem->wp_plugins_dir()),
fw_fix_path($themes_dir) => fw_fix_path($wp_filesystem->wp_themes_dir()),
);
FW_Cache::set($cache_key, $dirs);
return $dirs;
}
}
/**
* Convert real file path to WP Filesystem path
* @param string $real_path
* @return string|false
*/
final public static function real_path_to_filesystem_path($real_path) {
/** @var WP_Filesystem_Base $wp_filesystem */
global $wp_filesystem;
if (!$wp_filesystem) {
trigger_error('Filesystem is not available', E_USER_ERROR);
} elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
trigger_error('Filesystem: '. $wp_filesystem->errors->get_error_message(), E_USER_ERROR);
}
$real_path = fw_fix_path($real_path);
foreach (self::get_base_dirs_map() as $base_real_path => $base_wp_filesystem_path) {
$prefix_regex = '/^'. preg_quote($base_real_path, '/') .'($|\/.*)/';
// check if path is inside base path
if (!preg_match($prefix_regex, $real_path)) {
continue;
}
if ($base_real_path === '/') {
$relative_path = $real_path;
} else {
$relative_path = preg_replace($prefix_regex, '$1', $real_path);
}
return $base_wp_filesystem_path . $relative_path;
}
return false;
}
/**
* Convert WP Filesystem path to real file path
* @param string $wp_filesystem_path
* @return string|false
*/
final public static function filesystem_path_to_real_path($wp_filesystem_path) {
/** @var WP_Filesystem_Base $wp_filesystem */
global $wp_filesystem;
if (!$wp_filesystem) {
trigger_error('Filesystem is not available', E_USER_ERROR);
} elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
trigger_error('Filesystem: '. $wp_filesystem->errors->get_error_message(), E_USER_ERROR);
}
$wp_filesystem_path = fw_fix_path($wp_filesystem_path);
foreach (self::get_base_dirs_map() as $base_real_path => $base_wp_filesystem_path) {
$prefix_regex = '/^'. preg_quote($base_wp_filesystem_path, '/') .'($|\/.*)/';
// check if path is inside base path
if (!preg_match($prefix_regex, $wp_filesystem_path)) {
continue;
}
if ($base_wp_filesystem_path === '/') {
$relative_path = $wp_filesystem_path;
} else {
$relative_path = preg_replace($prefix_regex, '$1', $wp_filesystem_path);
}
return $base_real_path . $relative_path;
}
return false;
}
/**
* Check if there is direct filesystem access, so we can make changes without asking the credentials via form
* @param string|null $context
* @return bool
*/
final public static function has_direct_access($context = null)
{
/** @var WP_Filesystem_Base $wp_filesystem */
global $wp_filesystem;
if ($wp_filesystem) {
if (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
return false;
} else {
return $wp_filesystem->method === 'direct';
}
}
if (get_filesystem_method(array(), $context) === 'direct') {
ob_start();
{
$creds = request_filesystem_credentials(admin_url(), '', false, $context, null);
}
ob_end_clean();
if ( WP_Filesystem($creds) ) {
return true;
}
}
return false;
}
/**
* Create wp filesystem directory recursive
* @param string $wp_filesystem_dir_path
* @return bool
*/
final public static function mkdir_recursive($wp_filesystem_dir_path) {
/** @var WP_Filesystem_Base $wp_filesystem */
global $wp_filesystem;
if (!$wp_filesystem) {
trigger_error('Filesystem is not available', E_USER_ERROR);
} elseif (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
trigger_error('Filesystem: '. $wp_filesystem->errors->get_error_message(), E_USER_ERROR);
}
$wp_filesystem_dir_path = fw_fix_path($wp_filesystem_dir_path);
$path = false;
foreach (self::get_base_dirs_map() as $base_real_path => $base_wp_filesystem_path) {
$prefix_regex = '/^'. preg_quote($base_wp_filesystem_path, '/') .'($|\/)/';
// check if path is inside base path
if (!preg_match($prefix_regex, $wp_filesystem_dir_path)) {
continue;
}
$path = $base_wp_filesystem_path;
break;
}
if (!$path) {
trigger_error(
sprintf(
__('Cannot create directory "%s". It must be inside "%s"', 'fw'),
$wp_filesystem_dir_path,
implode(__('" or "', 'fw'), self::get_base_dirs_map())
),
E_USER_WARNING
);
return false;
}
if ($path === '/') {
$rel_path = $wp_filesystem_dir_path;
} else {
$rel_path = preg_replace('/^'. preg_quote($path, '/') .'($|\/.*)/', '$1', $wp_filesystem_dir_path);
}
// improvement: do not check directory for existence if it's known that sure it doesn't exist
$check_if_exists = true;
foreach (explode('/', ltrim($rel_path, '/')) as $dir_name) {
$path .= '/' . $dir_name;
// When WP FS abspath is '/', $path can be '//wp-content'. Fix it '/wp-content'
$path = fw_fix_path($path);
if ($check_if_exists) {
if ($wp_filesystem->is_dir($path)) {
// do nothing if exists
continue;
} else {
// do not check anymore, next directories sure doesn't exist
$check_if_exists = false;
}
}
if (!$wp_filesystem->mkdir($path)) {
return false;
}
}
return true;
}
/**
* @param $file_path
* @param $content
*
* @return bool|WP_Error
*/
public static function put( $file_path, $content ) {
self::init_file_system();
/** @var WP_Filesystem_Base $wp_filesystem */
global $wp_filesystem;
if ( ! $wp_filesystem->put_contents( $file_path, $content ) ) {
return new WP_Error( 'fs_error_put_content', esc_html__( 'Error writing to file: ', 'fw' ) . wp_basename( $file_path ) );
}
return true;
}
/**
* @param $file_path
*
* @return bool|mixed|WP_Error
*/
public static function get( $file_path ) {
self::init_file_system();
/** @var WP_Filesystem_Base $wp_filesystem */
global $wp_filesystem;
$content = $wp_filesystem->get_contents( $file_path );
if ( false === $content ) {
return new WP_Error( 'fs_error_get_content', esc_html__( 'Error to get content from file: ', 'fw' ) . wp_basename( $file_path ) );
}
return $content;
}
/**
* Initialize wp files system.
*/
public static function init_file_system() {
if ( self::is_ready() ) {
return;
}
include_once( ABSPATH . '/wp-admin/includes/file.php' );
WP_Filesystem();
}
/**
* If is initialized and has no errors
* @return bool
* @since 2.6.8
*/
public static function is_ready() {
/** @var WP_Filesystem_Base $wp_filesystem */
global $wp_filesystem;
return $wp_filesystem && is_wp_error($wp_filesystem->errors) && !$wp_filesystem->errors->get_error_code();
}
}

View File

@@ -0,0 +1,968 @@
<?php if ( ! defined( 'FW' ) ) {
die( 'Forbidden' );
}
/**
* Base class for displaying a list of items in an ajaxified HTML table.
*
* @package WordPress
* @subpackage List_Table
* @since 3.1.0
* @access private
*/
class FW_WP_List_Table {
/**
* The current list of items
*
* @since 3.1.0
* @var array
* @access protected
*/
var $items;
/**
* Various information about the current table
*
* @since 3.1.0
* @var array
* @access private
*/
var $_args;
/**
* Various information needed for displaying the pagination
*
* @since 3.1.0
* @var array
* @access private
*/
var $_pagination_args = array();
/**
* The current screen
*
* @since 3.1.0
* @var object
* @access protected
*/
var $screen;
/**
* Cached bulk actions
*
* @since 3.1.0
* @var array
* @access private
*/
var $_actions;
/**
* Cached pagination output
*
* @since 3.1.0
* @var string
* @access private
*/
var $_pagination;
/**
* Constructor. The child class should call this constructor from its own constructor
*
* @param array $args An associative array with information about the current table
* @access protected
*/
function __construct( $args = array() ) {
$args = wp_parse_args( $args, array(
'plural' => '',
'singular' => '',
'ajax' => false,
'screen' => null,
) );
$this->screen = convert_to_screen( $args['screen'] );
add_filter( "manage_{$this->screen->id}_columns", array( $this, 'get_columns' ), 0 );
if ( !$args['plural'] )
$args['plural'] = $this->screen->base;
$args['plural'] = sanitize_key( $args['plural'] );
$args['singular'] = sanitize_key( $args['singular'] );
$this->_args = $args;
if ( $args['ajax'] ) {
// wp_enqueue_script( 'list-table' );
add_action( 'admin_footer', array( $this, '_js_vars' ) );
}
}
/**
* Checks the current user's permissions
* @uses wp_die()
*
* @since 3.1.0
* @access public
* @abstract
*/
function ajax_user_can() {
die( 'function WP_List_Table::ajax_user_can() must be over-ridden in a sub-class.' );
}
/**
* Prepares the list of items for displaying.
* @uses WP_List_Table::set_pagination_args()
*
* @since 3.1.0
* @access public
* @abstract
*/
function prepare_items() {
die( 'function WP_List_Table::prepare_items() must be over-ridden in a sub-class.' );
}
/**
* An internal method that sets all the necessary pagination arguments
*
* @param array $args An associative array with information about the pagination
* @access protected
*/
function set_pagination_args( $args ) {
$args = wp_parse_args( $args, array(
'total_items' => 0,
'total_pages' => 0,
'per_page' => 0,
) );
if ( !$args['total_pages'] && $args['per_page'] > 0 )
$args['total_pages'] = ceil( $args['total_items'] / $args['per_page'] );
// redirect if page number is invalid and headers are not already sent
if ( ! headers_sent() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
exit;
}
$this->_pagination_args = $args;
}
/**
* Access the pagination args
*
* @since 3.1.0
* @access public
*
* @param string $key
* @return array
*/
function get_pagination_arg( $key ) {
if ( 'page' == $key )
return $this->get_pagenum();
if ( isset( $this->_pagination_args[$key] ) )
return $this->_pagination_args[$key];
}
/**
* Whether the table has items to display or not
*
* @since 3.1.0
* @access public
*
* @return bool
*/
function has_items() {
return !empty( $this->items );
}
/**
* Message to be displayed when there are no items
*
* @since 3.1.0
* @access public
*/
function no_items() {
_e( 'No items found.', 'fw' );
}
/**
* Display the search box.
*
* @since 3.1.0
* @access public
*
* @param string $text The search button text
* @param string $input_id The search input id
*/
function search_box( $text, $input_id ) {
if ( empty( $_REQUEST['s'] ) && !$this->has_items() )
return;
$input_id = $input_id . '-search-input';
if ( ! empty( $_REQUEST['orderby'] ) )
echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
if ( ! empty( $_REQUEST['order'] ) )
echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
if ( ! empty( $_REQUEST['post_mime_type'] ) )
echo '<input type="hidden" name="post_mime_type" value="' . esc_attr( $_REQUEST['post_mime_type'] ) . '" />';
if ( ! empty( $_REQUEST['detached'] ) )
echo '<input type="hidden" name="detached" value="' . esc_attr( $_REQUEST['detached'] ) . '" />';
?>
<p class="search-box">
<label class="screen-reader-text" for="<?php echo esc_attr($input_id) ?>"><?php echo $text; ?>:</label>
<input type="search" id="<?php echo esc_attr($input_id) ?>" name="s" value="<?php _admin_search_query(); ?>" />
<?php submit_button( $text, 'button', false, false, array('id' => 'search-submit') ); ?>
</p>
<?php
}
/**
* Get an associative array ( id => link ) with the list
* of views available on this table.
*
* @since 3.1.0
* @access protected
*
* @return array
*/
function get_views() {
return array();
}
/**
* Display the list of views available on this table.
*
* @since 3.1.0
* @access public
*/
function views() {
$views = $this->get_views();
/**
* Filter the list of available list table views.
*
* The dynamic portion of the hook name, $this->screen->id, refers
* to the ID of the current screen, usually a string.
*
* @since 3.5.0
*
* @param array $views An array of available list table views.
*/
$views = apply_filters( "views_{$this->screen->id}", $views );
if ( empty( $views ) )
return;
echo "<ul class='subsubsub'>\n";
foreach ( $views as $class => $view ) {
$views[ $class ] = "\t<li class='$class'>$view";
}
echo implode( " |</li>\n", $views ) . "</li>\n";
echo "</ul>";
}
/**
* Get an associative array ( option_name => option_title ) with the list
* of bulk actions available on this table.
*
* @since 3.1.0
* @access protected
*
* @return array
*/
function get_bulk_actions() {
return array();
}
/**
* Display the bulk actions dropdown.
*
* @since 3.1.0
* @access public
*/
function bulk_actions() {
if ( is_null( $this->_actions ) ) {
$no_new_actions = $this->_actions = $this->get_bulk_actions();
/**
* Filter the list table Bulk Actions drop-down.
*
* The dynamic portion of the hook name, $this->screen->id, refers
* to the ID of the current screen, usually a string.
*
* This filter can currently only be used to remove bulk actions.
*
* @since 3.5.0
*
* @param array $actions An array of the available bulk actions.
*/
$this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions );
$this->_actions = array_intersect_assoc( $this->_actions, $no_new_actions );
$two = '';
} else {
$two = '2';
}
if ( empty( $this->_actions ) )
return;
echo "<select name='action$two'>\n";
echo "<option value='-1' selected='selected'>" . __( 'Bulk Actions', 'fw' ) . "</option>\n";
foreach ( $this->_actions as $name => $title ) {
$class = 'edit' == $name ? ' class="hide-if-no-js"' : '';
echo "\t<option value='$name'$class>$title</option>\n";
}
echo "</select>\n";
submit_button( __( 'Apply', 'fw' ), 'action', false, false, array( 'id' => "doaction$two" ) );
echo "\n";
}
/**
* Get the current action selected from the bulk actions dropdown.
*
* @since 3.1.0
* @access public
*
* @return string|bool The action name or False if no action was selected
*/
function current_action() {
if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] )
return $_REQUEST['action'];
if ( isset( $_REQUEST['action2'] ) && -1 != $_REQUEST['action2'] )
return $_REQUEST['action2'];
return false;
}
/**
* Generate row actions div
*
* @since 3.1.0
* @access protected
*
* @param array $actions The list of actions
* @param bool $always_visible Whether the actions should be always visible
* @return string
*/
function row_actions( $actions, $always_visible = false ) {
$action_count = count( $actions );
$i = 0;
if ( !$action_count )
return '';
$out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
foreach ( $actions as $action => $link ) {
++$i;
( $i == $action_count ) ? $sep = '' : $sep = ' | ';
$out .= "<span class='$action'>$link$sep</span>";
}
$out .= '</div>';
return $out;
}
/**
* Display a monthly dropdown for filtering items
*
* @since 3.1.0
* @access protected
*/
function months_dropdown( $post_type ) {
global $wpdb, $wp_locale;
$months = $wpdb->get_results( $wpdb->prepare( "
SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
FROM $wpdb->posts
WHERE post_type = %s
ORDER BY post_date DESC
", $post_type ) );
/**
* Filter the 'Months' drop-down results.
*
* @since 3.7.0
*
* @param object $months The months drop-down query results.
* @param string $post_type The post type.
*/
$months = apply_filters( 'months_dropdown_results', $months, $post_type );
$month_count = count( $months );
if ( !$month_count || ( 1 == $month_count && 0 == $months[0]->month ) )
return;
$m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0;
?>
<select name='m'>
<option<?php selected( $m, 0 ); ?> value='0'><?php _e( 'All dates', 'fw' ); ?></option>
<?php
foreach ( $months as $arc_row ) {
if ( 0 == $arc_row->year )
continue;
$month = zeroise( $arc_row->month, 2 );
$year = $arc_row->year;
printf( "<option %s value='%s'>%s</option>\n",
selected( $m, $year . $month, false ),
esc_attr( $arc_row->year . $month ),
/* translators: 1: month name, 2: 4-digit year */
sprintf( __( '%1$s %2$d', 'fw' ), $wp_locale->get_month( $month ), $year )
);
}
?>
</select>
<?php
}
/**
* Display a view switcher
*
* @since 3.1.0
* @access protected
*/
function view_switcher( $current_mode ) {
$modes = array(
'list' => __( 'List View', 'fw' ),
'excerpt' => __( 'Excerpt View', 'fw' )
);
?>
<input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" />
<div class="view-switch">
<?php
foreach ( $modes as $mode => $title ) {
$class = ( $current_mode == $mode ) ? 'class="current"' : '';
echo "<a href='" . esc_url( add_query_arg( 'mode', $mode, $_SERVER['REQUEST_URI'] ) ) . "' $class><img id='view-switch-$mode' src='" . esc_url( includes_url( 'images/blank.gif' ) ) . "' width='20' height='20' title='$title' alt='$title' /></a>\n";
}
?>
</div>
<?php
}
/**
* Display a comment count bubble
*
* @since 3.1.0
* @access protected
*
* @param int $post_id
* @param int $pending_comments
*/
function comments_bubble( $post_id, $pending_comments ) {
$pending_phrase = sprintf( __( '%s pending', 'fw' ), number_format( $pending_comments ) );
if ( $pending_comments )
echo '<strong>';
echo "<a href='" . esc_url( add_query_arg( 'p', $post_id, admin_url( 'edit-comments.php' ) ) ) . "' title='" . esc_attr( $pending_phrase ) . "' class='post-com-count'><span class='comment-count'>" . number_format_i18n( get_comments_number() ) . "</span></a>";
if ( $pending_comments )
echo '</strong>';
}
/**
* Get the current page number
*
* @since 3.1.0
* @access protected
*
* @return int
*/
function get_pagenum() {
$pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
if( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] )
$pagenum = $this->_pagination_args['total_pages'];
return max( 1, $pagenum );
}
/**
* Get number of items to display on a single page
*
* @since 3.1.0
* @access protected
*
* @return int
*/
function get_items_per_page( $option, $default = 20 ) {
$per_page = (int) get_user_option( $option );
if ( empty( $per_page ) || $per_page < 1 )
$per_page = $default;
/**
* Filter the number of items to be displayed on each page of the list table.
*
* The dynamic hook name, $option, refers to the per page option depending
* on the type of list table in use. Possible values may include:
* 'edit_comments_per_page', 'sites_network_per_page', 'site_themes_network_per_page',
* 'themes_netework_per_page', 'users_network_per_page', 'edit_{$post_type}', etc.
*
* @since 2.9.0
*
* @param int $per_page Number of items to be displayed. Default 20.
*/
return (int) apply_filters( $option, $per_page );
}
/**
* Display the pagination.
*
* @since 3.1.0
* @access protected
*/
function pagination( $which ) {
if ( empty( $this->_pagination_args ) )
return;
extract( $this->_pagination_args, EXTR_SKIP );
$output = '<span class="displaying-num">' . sprintf( _n( '1 item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>';
$current = $this->get_pagenum();
$current_url = fw_current_url();
$current_url = remove_query_arg( array( 'hotkeys_highlight_last', 'hotkeys_highlight_first' ), $current_url );
$page_links = array();
$disable_first = $disable_last = '';
if ( $current == 1 )
$disable_first = ' disabled';
if ( $current == $total_pages )
$disable_last = ' disabled';
$page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
'first-page' . $disable_first,
esc_attr__( 'Go to the first page', 'fw' ),
esc_url( remove_query_arg( 'paged', $current_url ) ),
'&laquo;'
);
$page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
'prev-page' . $disable_first,
esc_attr__( 'Go to the previous page', 'fw' ),
esc_url( add_query_arg( 'paged', max( 1, $current-1 ), $current_url ) ),
'&lsaquo;'
);
if ( 'bottom' == $which )
$html_current_page = $current;
else
$html_current_page = sprintf( "<input class='current-page' title='%s' type='text' name='paged' value='%s' size='%d' />",
esc_attr__( 'Current page', 'fw' ),
$current,
strlen( $total_pages )
);
$html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
$page_links[] = '<span class="paging-input">' . sprintf( _x( '%1$s of %2$s', 'paging', 'fw' ), $html_current_page, $html_total_pages ) . '</span>';
$page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
'next-page' . $disable_last,
esc_attr__( 'Go to the next page', 'fw' ),
esc_url( add_query_arg( 'paged', min( $total_pages, $current+1 ), $current_url ) ),
'&rsaquo;'
);
$page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
'last-page' . $disable_last,
esc_attr__( 'Go to the last page', 'fw' ),
esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
'&raquo;'
);
$pagination_links_class = 'pagination-links';
if ( ! empty( $infinite_scroll ) )
$pagination_links_class = ' hide-if-js';
$output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';
if ( $total_pages )
$page_class = $total_pages < 2 ? ' one-page' : '';
else
$page_class = ' no-pages';
$this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";
echo $this->_pagination;
}
/**
* Get a list of columns. The format is:
* 'internal-name' => 'Title'
*
* @since 3.1.0
* @access protected
* @abstract
*
* @return array
*/
function get_columns() {
die( 'function WP_List_Table::get_columns() must be over-ridden in a sub-class.' );
}
/**
* Get a list of sortable columns. The format is:
* 'internal-name' => 'orderby'
* or
* 'internal-name' => array( 'orderby', true )
*
* The second format will make the initial sorting order be descending
*
* @since 3.1.0
* @access protected
*
* @return array
*/
function get_sortable_columns() {
return array();
}
/**
* Get a list of all, hidden and sortable columns, with filter applied
*
* @since 3.1.0
* @access protected
*
* @return array
*/
function get_column_info() {
if ( isset( $this->_column_headers ) )
return $this->_column_headers;
$columns = get_column_headers( $this->screen );
$hidden = get_hidden_columns( $this->screen );
$sortable_columns = $this->get_sortable_columns();
/**
* Filter the list table sortable columns for a specific screen.
*
* The dynamic portion of the hook name, $this->screen->id, refers
* to the ID of the current screen, usually a string.
*
* @since 3.5.0
*
* @param array $sortable_columns An array of sortable columns.
*/
$_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
$sortable = array();
foreach ( $_sortable as $id => $data ) {
if ( empty( $data ) )
continue;
$data = (array) $data;
if ( !isset( $data[1] ) )
$data[1] = false;
$sortable[$id] = $data;
}
$this->_column_headers = array( $columns, $hidden, $sortable );
return $this->_column_headers;
}
/**
* Return number of visible columns
*
* @since 3.1.0
* @access public
*
* @return int
*/
function get_column_count() {
list ( $columns, $hidden ) = $this->get_column_info();
$hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
return count( $columns ) - count( $hidden );
}
/**
* Print column headers, accounting for hidden and sortable columns.
*
* @since 3.1.0
* @access protected
*
* @param bool $with_id Whether to set the id attribute or not
*/
function print_column_headers( $with_id = true ) {
list( $columns, $hidden, $sortable ) = $this->get_column_info();
$current_url = fw_current_url();
$current_url = remove_query_arg( 'paged', $current_url );
if ( isset( $_GET['orderby'] ) )
$current_orderby = $_GET['orderby'];
else
$current_orderby = '';
if ( isset( $_GET['order'] ) && 'desc' == $_GET['order'] )
$current_order = 'desc';
else
$current_order = 'asc';
if ( ! empty( $columns['cb'] ) ) {
static $cb_counter = 1;
$columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All', 'fw' ) . '</label>'
. '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
$cb_counter++;
}
foreach ( $columns as $column_key => $column_display_name ) {
$class = array( 'manage-column', "column-$column_key" );
$style = '';
if ( in_array( $column_key, $hidden ) )
$style = 'display:none;';
$style = ' style="' . $style . '"';
if ( 'cb' == $column_key )
$class[] = 'check-column';
elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) )
$class[] = 'num';
if ( isset( $sortable[$column_key] ) ) {
list( $orderby, $desc_first ) = $sortable[$column_key];
if ( $current_orderby == $orderby ) {
$order = 'asc' == $current_order ? 'desc' : 'asc';
$class[] = 'sorted';
$class[] = $current_order;
} else {
$order = $desc_first ? 'desc' : 'asc';
$class[] = 'sortable';
$class[] = $desc_first ? 'asc' : 'desc';
}
$column_display_name = '<a href="' . esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ) . '"><span>' . $column_display_name . '</span><span class="sorting-indicator"></span></a>';
}
$id = $with_id ? "id='$column_key'" : '';
if ( !empty( $class ) )
$class = "class='" . join( ' ', $class ) . "'";
echo "<th scope='col' $id $class $style>$column_display_name</th>";
}
}
/**
* Display the table
*
* @since 3.1.0
* @access public
*/
function display() {
extract( $this->_args );
$this->display_tablenav( 'top' );
?>
<table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
<thead>
<tr>
<?php $this->print_column_headers(); ?>
</tr>
</thead>
<tfoot>
<tr>
<?php $this->print_column_headers( false ); ?>
</tr>
</tfoot>
<tbody id="the-list"<?php if ( $singular ) echo " data-wp-lists='list:$singular'"; ?>>
<?php $this->display_rows_or_placeholder(); ?>
</tbody>
</table>
<?php
$this->display_tablenav( 'bottom' );
}
/**
* Get a list of CSS classes for the <table> tag
*
* @since 3.1.0
* @access protected
*
* @return array
*/
function get_table_classes() {
return array( 'widefat', 'fixed', $this->_args['plural'] );
}
/**
* Generate the table navigation above or below the table
*
* @since 3.1.0
* @access protected
*/
function display_tablenav( $which ) {
if ( 'top' == $which )
wp_nonce_field( 'bulk-' . $this->_args['plural'] );
?>
<div class="tablenav <?php echo esc_attr( $which ); ?>">
<div class="alignleft actions bulkactions">
<?php $this->bulk_actions(); ?>
</div>
<?php
$this->extra_tablenav( $which );
$this->pagination( $which );
?>
<br class="clear" />
</div>
<?php
}
/**
* Extra controls to be displayed between bulk actions and pagination
*
* @since 3.1.0
* @access protected
*/
function extra_tablenav( $which ) {}
/**
* Generate the <tbody> part of the table
*
* @since 3.1.0
* @access protected
*/
function display_rows_or_placeholder() {
if ( $this->has_items() ) {
$this->display_rows();
} else {
list( $columns, $hidden ) = $this->get_column_info();
echo '<tr class="no-items"><td class="colspanchange" colspan="' . $this->get_column_count() . '">';
$this->no_items();
echo '</td></tr>';
}
}
/**
* Generate the table rows
*
* @since 3.1.0
* @access protected
*/
function display_rows() {
foreach ( $this->items as $item )
$this->single_row( $item );
}
/**
* Generates content for a single row of the table
*
* @since 3.1.0
* @access protected
*
* @param object $item The current item
*/
function single_row( $item ) {
static $row_class = '';
$row_class = ( $row_class == '' ? ' class="alternate"' : '' );
echo '<tr' . $row_class . '>';
$this->single_row_columns( $item );
echo '</tr>';
}
/**
* Generates the columns for a single row of the table
*
* @since 3.1.0
* @access protected
*
* @param object $item The current item
*/
function single_row_columns( $item ) {
list( $columns, $hidden ) = $this->get_column_info();
foreach ( $columns as $column_name => $column_display_name ) {
$class = "class='$column_name column-$column_name'";
$style = '';
if ( in_array( $column_name, $hidden ) )
$style = ' style="display:none;"';
$attributes = "$class$style";
if ( 'cb' == $column_name ) {
echo '<th scope="row" class="check-column">';
echo $this->column_cb( $item );
echo '</th>';
}
elseif ( method_exists( $this, 'column_' . $column_name ) ) {
echo "<td $attributes>";
echo call_user_func( array( $this, 'column_' . $column_name ), $item );
echo "</td>";
}
else {
echo "<td $attributes>";
echo $this->column_default( $item, $column_name );
echo "</td>";
}
}
}
/**
* Handle an incoming ajax request (called from admin-ajax.php)
*
* @since 3.1.0
* @access public
*/
function ajax_response() {
$this->prepare_items();
extract( $this->_args );
extract( $this->_pagination_args, EXTR_SKIP );
ob_start();
if ( ! empty( $_REQUEST['no_placeholder'] ) )
$this->display_rows();
else
$this->display_rows_or_placeholder();
$rows = ob_get_clean();
$response = array( 'rows' => $rows );
if ( isset( $total_items ) )
$response['total_items_i18n'] = sprintf( _n( '1 item', '%s items', $total_items ), number_format_i18n( $total_items ) );
if ( isset( $total_pages ) ) {
$response['total_pages'] = $total_pages;
$response['total_pages_i18n'] = number_format_i18n( $total_pages );
}
die( json_encode( $response ) );
}
/**
* Send required variables to JavaScript land
*
* @access private
*/
function _js_vars() {
$args = array(
'class' => get_class( $this ),
'screen' => array(
'id' => $this->screen->id,
'base' => $this->screen->base,
)
);
printf( "<script type='text/javascript'>list_args = %s;</script>\n", json_encode( $args ) );
}
}

View File

@@ -0,0 +1,66 @@
<?php if ( ! defined( 'FW' ) ) {
die( 'Forbidden' );
}
/**
* Features:
* - Works with "multi keys"
*/
class FW_WP_Meta {
/**
* @param string $meta_type
* @param int $object_id
* @param string $multi_key 'abc' or 'ab/c/def'
* @param array|string|int|bool $set_value
*/
public static function set( $meta_type, $object_id, $multi_key, $set_value ) {
if ( empty( $multi_key ) ) {
trigger_error( 'Key not specified', E_USER_WARNING );
return;
}
$multi_key = explode( '/', $multi_key );
$key = array_shift( $multi_key );
$multi_key = implode( '/', $multi_key );
if ( empty( $multi_key ) && $multi_key !== '0' ) { // Replace entire meta
fw_update_metadata( $meta_type, $object_id, $key, $set_value );
} else { // Change only specified key
$value = self::get( $meta_type, $object_id, $key, true );
fw_aks( $multi_key, $set_value, $value );
fw_update_metadata( $meta_type, $object_id, $key, $value );
}
}
/**
* @param string $meta_type
* @param int $object_id
* @param string $multi_key 'abc' or 'ab/c/def'
* @param null|mixed $default_value If no option found in the database, this value will be returned
* @param bool|null $get_original_value REMOVED https://github.com/ThemeFuse/Unyson/issues/1676
*
* @return mixed|null
*/
public static function get( $meta_type, $object_id, $multi_key, $default_value = null, $get_original_value = null ) {
if ( ! is_null($get_original_value) ) {
_doing_it_wrong(__FUNCTION__, '$get_original_value parameter was removed', 'Unyson 2.5.8');
}
if ( empty( $multi_key ) ) {
trigger_error( 'Key not specified', E_USER_WARNING );
return null;
}
$multi_key = explode( '/', $multi_key );
$key = array_shift( $multi_key );
$multi_key = implode( '/', $multi_key );
$value = get_metadata( $meta_type, $object_id, $key, true );
if ( empty( $multi_key ) && $multi_key !== '0' ) {
return $value;
} else {
return fw_akg($multi_key, $value, $default_value);
}
}
}

View File

@@ -0,0 +1,49 @@
<?php if (!defined('FW')) die('Forbidden');
/**
* Alternative to WordPress get_option() and update_option() functions
*
* Features:
* - Works with "multi keys"
*/
class FW_WP_Option
{
/**
* @param string $option_name
* @param string|null $specific_multi_key 'ab/c/def'
* @param null|mixed $default_value If no option found in the database, this value will be returned
* @param bool|null $get_original_value REMOVED https://github.com/ThemeFuse/Unyson/issues/1676
* @return mixed|null
*/
public static function get($option_name, $specific_multi_key = null, $default_value = null, $get_original_value = null)
{
if ( ! is_null($get_original_value) ) {
_doing_it_wrong(__FUNCTION__, '$get_original_value parameter was removed', 'Unyson 2.5.8');
}
$value = get_option($option_name, null);
if (empty($specific_multi_key) && $specific_multi_key !== '0') {
return is_null($value) ? fw_call( $default_value ) : $value;
} else {
return fw_akg($specific_multi_key, $value, $default_value);
}
}
/**
* Alternative for update_option()
* @param string $option_name
* @param string|null $specific_multi_key
* @param array|string|int|bool $set_value
*/
public static function set($option_name, $specific_multi_key = null, $set_value)
{
if ($specific_multi_key === null) { // Replace entire option
update_option($option_name, $set_value, false);
} else { // Change only specified key
$value = self::get($option_name, null, true);
fw_aks($specific_multi_key, $set_value, $value);
update_option($option_name, $value, false);
}
}
}

View File

@@ -0,0 +1,713 @@
<?php if ( ! defined( 'FW' ) ) {
die( 'Forbidden' );
}
// Theme Settings Options
class FW_Db_Options_Model_Settings extends FW_Db_Options_Model {
protected function get_id() {
return 'settings';
}
protected function get_options($item_id, array $extra_data = array()) {
return fw()->theme->get_settings_options();
}
protected function get_values($item_id, array $extra_data = array()) {
return FW_WP_Option::get('fw_theme_settings_options:'. fw()->theme->manifest->get_id(), null, array());
}
protected function set_values($item_id, $values, array $extra_data = array()) {
FW_WP_Option::set('fw_theme_settings_options:' . fw()->theme->manifest->get_id(), null, $values);
}
protected function get_fw_storage_params($item_id, array $extra_data = array()) {
return array(
'settings' => true
);
}
protected function _after_set($item_id, $option_id, $sub_keys, $old_value, array $extra_data = array()) {
/**
* @since 2.6.0
*/
do_action('fw_settings_options_update', array(
/**
* Option id
* First level multi-key
* For e.g. if $option_id is 'hello/world/7' this will be 'hello'
*/
'option_id' => $option_id,
/**
* The remaining sub-keys
* For e.g.
* if $option_id is 'hello/world/7' this will be array('world', '7')
* if $option_id is 'hello' this will be array()
*/
'sub_keys' => explode('/', $sub_keys),
/**
* Old option(s) value
*/
'old_value' => $old_value
));
}
protected function _init() {
/**
* Get a theme settings option value from the database
*
* @param string|null $option_id Specific option id (accepts multikey). null - all options
* @param null|mixed $default_value If no option found in the database, this value will be returned
* @param null|bool $get_original_value REMOVED https://github.com/ThemeFuse/Unyson/issues/1676
*
* @return mixed|null
*/
function fw_get_db_settings_option( $option_id = null, $default_value = null, $get_original_value = null ) {
return FW_Db_Options_Model_Settings::_get_instance('settings')->get(null, $option_id, $default_value);
}
/**
* Set a theme settings option value in database
*
* @param null $option_id Specific option id (accepts multikey). null - all options
* @param mixed $value
*/
function fw_set_db_settings_option( $option_id = null, $value ) {
FW_Db_Options_Model_Settings::_get_instance('settings')->set(null, $option_id, $value);
}
}
}
new FW_Db_Options_Model_Settings();
// Post Options
class FW_Db_Options_Model_Post extends FW_Db_Options_Model {
protected function get_id() {
return 'post';
}
private function get_cache_key($key, $item_id = null, array $extra_data = array()) {
return 'fw-options-model:'. $this->get_id() .'/'. $key;
}
private function get_post_id($post_id) {
$post_id = intval($post_id);
try {
// Prevent too often execution of wp_get_post_autosave() because it does WP Query
return FW_Cache::get($cache_key = $this->get_cache_key('id/'. $post_id));
} catch (FW_Cache_Not_Found_Exception $e) {
if ( ! $post_id ) {
/** @var WP_Post $post */
global $post;
if ( ! $post ) {
return null;
} else {
$post_id = $post->ID;
}
/**
* Check if is Preview and use the preview post_id instead of real/current post id
*
* Note: WordPress changes the global $post content on preview:
* 1. https://github.com/WordPress/WordPress/blob/2096b451c704715db3c4faf699a1184260deade9/wp-includes/query.php#L3573-L3583
* 2. https://github.com/WordPress/WordPress/blob/4a31dd6fe8b774d56f901a29e72dcf9523e9ce85/wp-includes/revision.php#L485-L528
*/
if ( is_preview() && is_object($preview = wp_get_post_autosave($post->ID)) ) {
$post_id = $preview->ID;
}
}
FW_Cache::set($cache_key, $post_id);
return $post_id;
}
}
private function get_post_type($post_id) {
$post_id = $this->get_post_id($post_id);
try {
return FW_Cache::get($cache_key = $this->get_cache_key('type/'. $post_id));
} catch (FW_Cache_Not_Found_Exception $e) {
FW_Cache::set(
$cache_key,
$post_type = get_post_type(
($post_revision_id = wp_is_post_revision($post_id)) ? $post_revision_id : $post_id
)
);
return $post_type;
}
}
protected function get_options($item_id, array $extra_data = array()) {
$post_type = $this->get_post_type($item_id);
if (apply_filters('fw_get_db_post_option:fw-storage-enabled',
/**
* Slider extension has too many fw_get_db_post_option()
* inside post options altering filter and it creates recursive mess.
* add_filter() was added in Slider extension
* but this hardcode can be replaced with `true`
* only after all users will install new version 1.1.15.
*/
$post_type !== 'fw-slider',
$post_type
)) {
return fw()->theme->get_post_options( $post_type );
} else {
return array();
}
}
protected function get_values($item_id, array $extra_data = array()) {
return FW_WP_Meta::get( 'post', $this->get_post_id($item_id), 'fw_options', array() );
}
protected function set_values($item_id, $values, array $extra_data = array()) {
FW_WP_Meta::set( 'post', $this->get_post_id($item_id), 'fw_options', $values );
}
protected function get_fw_storage_params($item_id, array $extra_data = array()) {
return array( 'post-id' => $this->get_post_id($item_id) );
}
protected function _get_cache_key($key, $item_id, array $extra_data = array()) {
if ($key === 'options') {
// Cache options grouped by post-type, not by post id
return ($post_type = $this->get_post_type($item_id)) ? $post_type : '?';
} else {
return $this->get_post_id($item_id);
}
}
protected function _after_set($post_id, $option_id, $sub_keys, $old_value, array $extra_data = array()) {
/**
* @deprecated
*/
fw()->backend->_sync_post_separate_meta($post_id);
/**
* @since 2.2.8
*/
do_action('fw_post_options_update',
$post_id,
/**
* Option id
* First level multi-key
* For e.g. if $option_id is 'hello/world/7' this will be 'hello'
*/
$option_id,
/**
* The remaining sub-keys
* For e.g.
* if $option_id is 'hello/world/7' this will be array('world', '7')
* if $option_id is 'hello' this will be array()
*/
explode('/', $sub_keys),
/**
* Old post option(s) value
* @since 2.3.3
*/
$old_value
);
}
protected function _init() {
/**
* Get post option value from the database
*
* @param null|int $post_id
* @param string|null $option_id Specific option id (accepts multikey). null - all options
* @param null|mixed $default_value If no option found in the database, this value will be returned
* @param null|bool $get_original_value REMOVED https://github.com/ThemeFuse/Unyson/issues/1676
*
* @return mixed|null
*/
function fw_get_db_post_option($post_id = null, $option_id = null, $default_value = null, $get_original_value = null) {
return FW_Db_Options_Model::_get_instance('post')->get(intval($post_id), $option_id, $default_value);
}
/**
* Set post option value in database
*
* @param null|int $post_id
* @param string|null $option_id Specific option id (accepts multikey). null - all options
* @param $value
*/
function fw_set_db_post_option( $post_id = null, $option_id = null, $value ) {
FW_Db_Options_Model::_get_instance('post')->set(intval($post_id), $option_id, $value);
}
// todo: add_action() to clear the FW_Cache
}
}
new FW_Db_Options_Model_Post();
// Term Options
class FW_Db_Options_Model_Term extends FW_Db_Options_Model {
protected function get_id() {
return 'term';
}
protected function get_values($item_id, array $extra_data = array()) {
self::migrate($item_id);
return (array)get_term_meta( $item_id, 'fw_options', true);
}
protected function set_values($item_id, $values, array $extra_data = array()) {
self::migrate($item_id);
update_term_meta($item_id, 'fw_options', $values);
}
protected function get_options($item_id, array $extra_data = array()) {
return fw()->theme->get_taxonomy_options($extra_data['taxonomy']);
}
protected function get_fw_storage_params($item_id, array $extra_data = array()) {
return array(
'term-id' => $item_id,
'taxonomy' => $extra_data['taxonomy'],
);
}
protected function _get_cache_key($key, $item_id, array $extra_data = array()) {
if ($key === 'options') {
return $extra_data['taxonomy']; // Cache options grouped by taxonomy, not by term id
} else {
return $item_id;
}
}
/**
* Cache termmeta table name if exists
* @var string|false
*/
private static $old_table_name;
/**
* @return string|false
*/
private static function get_old_table_name() {
if (is_null(self::$old_table_name)) {
/** @var WPDB $wpdb */
global $wpdb;
$table_name = $wpdb->get_results( "show tables like '{$wpdb->prefix}fw_termmeta'", ARRAY_A );
$table_name = $table_name ? array_pop($table_name[0]) : false;
if ( $table_name && ! $wpdb->get_results( "SELECT 1 FROM `{$table_name}` LIMIT 1" ) ) {
// The table is empty, delete it
$wpdb->query( "DROP TABLE `{$table_name}`" );
$table_name = false;
}
self::$old_table_name = $table_name;
}
return self::$old_table_name;
}
/**
* @internal
*/
public static function _action_switch_blog() {
self::$old_table_name = null; // reset
}
/**
* When a term is deleted, delete its meta from old fw_termmeta table
*
* @param mixed $term_id
*
* @return void
* @internal
*/
public static function _action_fw_delete_term( $term_id ) {
if ( ! ( $table_name = self::get_old_table_name() ) ) {
return;
}
$term_id = (int) $term_id;
if ( ! $term_id ) {
return;
}
/** @var WPDB $wpdb */
global $wpdb;
$wpdb->delete( $table_name, array( 'fw_term_id' => $term_id ), array( '%d' ) );
}
/**
* In WP 4.4 was introduced native term meta https://codex.wordpress.org/Version_4.4#For_Developers
* All data from old table must be migrated to native term meta
* @param int $term_id
* @return bool
*/
private static function migrate($term_id) {
global $wpdb; /** @var wpdb $wpdb */
if (
( $old_table_name = self::get_old_table_name() )
&&
( $value = $wpdb->get_col( $wpdb->prepare(
"SELECT meta_value FROM `{$old_table_name}` WHERE fw_term_id = %d AND meta_key = 'fw_options' LIMIT 1",
$term_id
) ) )
&&
( $value = unserialize( $value[0] ) )
) {
$wpdb->delete( $old_table_name, array( 'fw_term_id' => $term_id ), array( '%d' ) );
update_term_meta( $term_id, 'fw_options', $value );
return true;
} else {
return false;
}
}
protected function _after_set($item_id, $option_id, $sub_keys, $old_value, array $extra_data = array()) {
/**
* @since 2.6.0
*/
do_action('fw_term_options_update', array(
'term_id' => $item_id,
'taxonomy' => $extra_data['taxonomy'],
/**
* Option id
* First level multi-key
* For e.g. if $option_id is 'hello/world/7' this will be 'hello'
*/
'option_id' => $option_id,
/**
* The remaining sub-keys
* For e.g.
* if $option_id is 'hello/world/7' this will be array('world', '7')
* if $option_id is 'hello' this will be array()
*/
'sub_keys' => explode('/', $sub_keys),
/**
* Old option(s) value
*/
'old_value' => $old_value
));
}
protected function _init() {
/**
* Get term option value from the database
*
* @param int $term_id
* @param string $taxonomy
* @param string|null $option_id Specific option id (accepts multikey). null - all options
* @param null|mixed $default_value If no option found in the database, this value will be returned
* @param null|bool $get_original_value REMOVED https://github.com/ThemeFuse/Unyson/issues/1676
*
* @return mixed|null
*/
function fw_get_db_term_option( $term_id, $taxonomy, $option_id = null, $default_value = null, $get_original_value = null ) {
if ( ! taxonomy_exists( $taxonomy ) ) {
return null;
}
return FW_Db_Options_Model::_get_instance('term')->get(intval($term_id), $option_id, $default_value, array(
'taxonomy' => $taxonomy
));
}
/**
* Set term option value in database
*
* @param int $term_id
* @param string $taxonomy
* @param string|null $option_id Specific option id (accepts multikey). null - all options
* @param mixed $value
*
* @return null
*/
function fw_set_db_term_option( $term_id, $taxonomy, $option_id = null, $value ) {
if ( ! taxonomy_exists( $taxonomy ) ) {
return null;
}
FW_Db_Options_Model::_get_instance('term')->set(intval($term_id), $option_id, $value, array(
'taxonomy' => $taxonomy
));
}
add_action( 'switch_blog', array( __CLASS__, '_action_switch_blog' ) );
add_action( 'delete_term', array( __CLASS__, '_action_fw_delete_term' ) );
}
}
new FW_Db_Options_Model_Term();
// Extensions Settings Options
class FW_Db_Options_Model_Extension extends FW_Db_Options_Model {
protected function get_id() {
return 'extension';
}
protected function get_values($item_id, array $extra_data = array()) {
return FW_WP_Option::get( 'fw_ext_settings_options:' . $item_id, null, array() );
}
protected function set_values($item_id, $values, array $extra_data = array()) {
FW_WP_Option::set( 'fw_ext_settings_options:' . $item_id, null, $values );
}
protected function get_options($item_id, array $extra_data = array()) {
return fw_ext($item_id)->get_settings_options();
}
protected function get_fw_storage_params($item_id, array $extra_data = array()) {
return array(
'extension' => $item_id,
);
}
protected function _init() {
/**
* Get extension's settings option value from the database
*
* @param string $extension_name
* @param string|null $option_id
* @param null|mixed $default_value If no option found in the database, this value will be returned
* @param null|bool $get_original_value REMOVED https://github.com/ThemeFuse/Unyson/issues/1676
*
* @return mixed|null
*/
function fw_get_db_ext_settings_option( $extension_name, $option_id = null, $default_value = null, $get_original_value = null ) {
if ( ! ( $extension = fw_ext( $extension_name ) ) ) {
trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
return null;
}
return FW_Db_Options_Model::_get_instance('extension')->get($extension_name, $option_id, $default_value);
}
/**
* Set extension's setting option value in database
*
* @param string $extension_name
* @param string|null $option_id
* @param mixed $value
*/
function fw_set_db_ext_settings_option( $extension_name, $option_id = null, $value ) {
if ( ! fw_ext( $extension_name ) ) {
trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
return;
}
FW_Db_Options_Model::_get_instance('extension')->set($extension_name, $option_id, $value);
}
}
}
new FW_Db_Options_Model_Extension();
// Customizer Options
class FW_Db_Options_Model_Customizer extends FW_Db_Options_Model {
protected function get_id() {
return 'customizer';
}
protected function get_values($item_id, array $extra_data = array()) {
return get_theme_mod('fw_options', array());
}
protected function set_values($item_id, $values, array $extra_data = array()) {
set_theme_mod('fw_options', $values);
}
protected function get_options($item_id, array $extra_data = array()) {
return fw()->theme->get_customizer_options();
}
protected function get_fw_storage_params($item_id, array $extra_data = array()) {
return array(
'customizer' => true
);
}
protected function _after_set($item_id, $option_id, $sub_keys, $old_value, array $extra_data = array()) {
/**
* @since 2.6.0
*/
do_action('fw_customizer_options_update', array(
/**
* Option id
* First level multi-key
* For e.g. if $option_id is 'hello/world/7' this will be 'hello'
*/
'option_id' => $option_id,
/**
* The remaining sub-keys
* For e.g.
* if $option_id is 'hello/world/7' this will be array('world', '7')
* if $option_id is 'hello' this will be array()
*/
'sub_keys' => explode('/', $sub_keys),
/**
* Old option(s) value
*/
'old_value' => $old_value
));
}
/**
* @internal
*/
public function _reset_cache() {
FW_Cache::del($this->get_main_cache_key());
}
protected function _init() {
/**
* Get a customizer framework option value from the database
*
* @param string|null $option_id Specific option id (accepts multikey). null - all options
* @param null|mixed $default_value If no option found in the database, this value will be returned
*
* @return mixed|null
*/
function fw_get_db_customizer_option( $option_id = null, $default_value = null ) {
return FW_Db_Options_Model::_get_instance('customizer')->get(null, $option_id, $default_value);
}
/**
* Set a theme customizer option value in database
*
* @param null $option_id Specific option id (accepts multikey). null - all options
* @param mixed $value
*/
function fw_set_db_customizer_option( $option_id = null, $value ) {
FW_Db_Options_Model::_get_instance('customizer')->set(null, $option_id, $value);
}
// Fixes https://github.com/ThemeFuse/Unyson/issues/2053
add_action('customize_preview_init', array($this, '_reset_cache'),
1 // Fixes https://github.com/ThemeFuse/Unyson/issues/2104
);
}
}
new FW_Db_Options_Model_Customizer();
{
/**
* Get user meta set by specific extension
*
* @param int $user_id
* @param string $extension_name
* @param string $keys
*
* If the extension doesn't exist or is disabled, or meta key doesn't exist, returns null,
* else returns the meta key value
*
* @return mixed|null
*/
function fw_get_db_extension_user_data( $user_id, $extension_name, $keys = null ) {
if ( ! fw()->extensions->get( $extension_name ) ) {
trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
return null;
}
$data = get_user_meta( $user_id, 'fw_data', true );
if ( is_null( $keys ) ) {
return fw_akg( $extension_name, $data );
}
return fw_akg( $extension_name . '/' . $keys, $data );
}
/**
* @param int $user_id
* @param string $extension_name
* @param mixed $value
* @param string $keys
*
* In case the extension doesn't exist or is disabled, or the value is equal to previous, returns false
*
* @return bool|int
*/
function fw_set_db_extension_user_data( $user_id, $extension_name, $value, $keys = null ) {
if ( ! fw()->extensions->get( $extension_name ) ) {
trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
return false;
}
$data = get_user_meta( $user_id, 'fw_data', true );
if ( $keys == null ) {
fw_aks( $extension_name, $value, $data );
} else {
fw_aks( $extension_name . '/' . $keys, $value, $data );
}
return fw_update_user_meta( $user_id, 'fw_data', $data );
}
}
/**
* Extensions Data
*
* Used by extensions to store custom data in database.
* By using these functions, extension prevent database spam with wp options for each extension,
* because these functions store all data in one wp option.
*/
{
/**
* Get extension's data from the database
*
* @param string $extension_name
* @param string|null $multi_key The key of the data you want to get. null - all data
* @param null|mixed $default_value If no option found in the database, this value will be returned
* @param null|bool $get_original_value REMOVED https://github.com/ThemeFuse/Unyson/issues/1676
*
* @return mixed|null
*/
function fw_get_db_extension_data( $extension_name, $multi_key = null, $default_value = null, $get_original_value = null ) {
if ( ! fw()->extensions->get( $extension_name ) ) {
trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
return null;
}
if ( $multi_key ) {
$multi_key = $extension_name . '/' . $multi_key;
} else {
$multi_key = $extension_name;
}
return FW_WP_Option::get( 'fw_extensions', $multi_key, $default_value, $get_original_value );
}
/**
* Set some extension's data in database
*
* @param string $extension_name
* @param string|null $multi_key The key of the data you want to set. null - all data
* @param mixed $value
*/
function fw_set_db_extension_data( $extension_name, $multi_key = null, $value ) {
if ( ! fw()->extensions->get( $extension_name ) ) {
trigger_error( 'Invalid extension: ' . $extension_name, E_USER_WARNING );
return;
}
if ( $multi_key ) {
$multi_key = $extension_name . '/' . $multi_key;
} else {
$multi_key = $extension_name;
}
FW_WP_Option::set( 'fw_extensions', $multi_key, $value );
}
}

View File

@@ -0,0 +1,22 @@
<?php if ( ! defined( 'FW' ) ) {
die( 'Forbidden' );
}
class FW_Form_Invalid_Submission_Exception extends Exception {
private $errors = array();
public function __construct( array $errors ) {
parent::__construct();
$this->set_errors( $errors );
}
public function get_errors() {
return $this->errors;
}
protected function set_errors( array $errors ) {
$this->errors = $errors;
}
}

View File

@@ -0,0 +1,7 @@
<?php if ( ! defined( 'FW' ) ) {
die( 'Forbidden' );
}
class FW_Form_Not_Found_Exception extends Exception {
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,116 @@
<?php if (!defined('FW')) die('Forbidden');
// Process the `fw-storage` option parameter
/**
* @param string $id
* @param array $option
* @param mixed $value
* @param array $params
*
* @return mixed
*
* @since 2.5.0
*/
function fw_db_option_storage_save($id, array $option, $value, array $params = array()) {
if (
!empty($option['fw-storage'])
&&
($storage = is_array($option['fw-storage'])
? $option['fw-storage']
: array('type' => $option['fw-storage'])
)
&&
!empty($storage['type'])
&&
($storage_type = fw_db_option_storage_type($storage['type']))
) {
$option['fw-storage'] = $storage;
} else {
return $value;
}
/** @var FW_Option_Storage_Type $storage_type */
return $storage_type->save($id, $option, $value, $params);
}
/**
* @param string $id
* @param array $option
* @param mixed $value
* @param array $params
*
* @return mixed
*
* @since 2.5.0
*/
function fw_db_option_storage_load($id, array $option, $value, array $params = array()) {
if (
!empty($option['fw-storage'])
&&
($storage = is_array($option['fw-storage'])
? $option['fw-storage']
: array('type' => $option['fw-storage'])
)
&&
!empty($storage['type'])
&&
($storage_type = fw_db_option_storage_type($storage['type']))
) {
// Fixes https://github.com/ThemeFuse/Unyson/issues/2265
if (isset($params['customizer']) && is_customize_preview()) {
/** @var WP_Customize_Manager $wp_customize */
global $wp_customize;
if (
($setting = $wp_customize->get_setting($setting_id = 'fw_options[' . $id . ']'))
&&
!is_null($wp_customize->post_value($setting))
) {
// Use POST preview value
return $value;
}
}
$option['fw-storage'] = $storage;
} else {
return $value;
}
/** @var FW_Option_Storage_Type $storage_type */
return $storage_type->load($id, $option, $value, $params);
}
/**
* @param null|string $type
* @return FW_Option_Storage_Type|FW_Option_Storage_Type[]|null
* @since 2.5.0
*/
function fw_db_option_storage_type($type = null) {
static $types = null;
if (is_null($types)) {
$access_key = new FW_Access_Key('fw:option-storage-register');
$register = new _FW_Option_Storage_Type_Register($access_key->get_key());
{
$register->register(new FW_Option_Storage_Type_WP_Option());
$register->register(new FW_Option_Storage_Type_Post_Meta());
$register->register(new FW_Option_Storage_Type_Term_Meta());
}
do_action('fw:option-storage-types:register', $register);
$types = $register->_get_types($access_key);
}
if (empty($type)) {
return $types;
} elseif (isset($types[$type])) {
return $types[$type];
} else {
return null;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,663 @@
<?php if ( ! defined( 'FW' ) ) {
die( 'Forbidden' );
}
/**
* Wordpress alternatives
* update_post_meta() strip slashes and it's impossible to save json or "\'" in post meta
* https://core.trac.wordpress.org/ticket/21767
*/
/**
* Add metadata for the specified object.
*
* @uses $wpdb WordPress database object for queries.
*
* @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
* @param int $object_id ID of the object metadata is for
* @param string $meta_key Metadata key
* @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
* @param bool $unique Optional, default is false. Whether the specified metadata key should be
* unique for the object. If true, and the object already has a value for the specified
* metadata key, no change will be made
*
* @return int|bool The meta ID on success, false on failure.
*/
function fw_add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = false ) {
/**
* @var WPDB $wpdb
*/
global $wpdb;
if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) {
return false;
}
$object_id = absint( $object_id );
if ( ! $object_id ) {
return false;
}
$table = _get_meta_table( $meta_type );
if ( ! $table ) {
return false;
}
$column = sanitize_key( $meta_type . '_id' );
// expected_slashed ($meta_key)
//$meta_key = wp_unslash($meta_key);
//$meta_value = wp_unslash($meta_value);
$meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
/**
* Filter whether to add metadata of a specific type.
*
* The dynamic portion of the hook, $meta_type, refers to the meta
* object type (comment, post, or user). Returning a non-null value
* will effectively short-circuit the function.
*
* @param null|bool $check Whether to allow adding metadata for the given type.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value. Must be serializable if non-scalar.
* @param bool $unique Whether the specified meta key should be unique
* for the object. Optional. Default false.
*/
$check = apply_filters( "add_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $unique );
if ( null !== $check ) {
return $check;
}
if ( $unique && $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM $table WHERE meta_key = %s AND $column = %d LIMIT 1",
$meta_key, $object_id ) )
) {
return false;
}
$_meta_value = $meta_value;
$meta_value = maybe_serialize( $meta_value );
/**
* Fires immediately before meta of a specific type is added.
*
* The dynamic portion of the hook, $meta_type, refers to the meta
* object type (comment, post, or user).
*
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
*/
do_action( "add_{$meta_type}_meta", $object_id, $meta_key, $_meta_value );
$result = $wpdb->insert( $table, array(
$column => $object_id,
'meta_key' => $meta_key,
'meta_value' => $meta_value,
) );
if ( ! $result ) {
return false;
}
$mid = (int) $wpdb->insert_id;
wp_cache_delete( $object_id, $meta_type . '_meta' );
/**
* Fires immediately after meta of a specific type is added.
*
* The dynamic portion of the hook, $meta_type, refers to the meta
* object type (comment, post, or user).
*
* @param int $mid The meta ID after successful update.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
*/
do_action( "added_{$meta_type}_meta", $mid, $object_id, $meta_key, $_meta_value );
return $mid;
}
/**
* Update metadata for the specified object. If no value already exists for the specified object
* ID and metadata key, the metadata will be added.
*
* @uses $wpdb WordPress database object for queries.
*
* @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
* @param int $object_id ID of the object metadata is for
* @param string $meta_key Metadata key
* @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
* @param mixed $prev_value Optional. If specified, only update existing metadata entries with
* the specified value. Otherwise, update all entries.
*
* @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
*/
function fw_update_metadata( $meta_type, $object_id, $meta_key, $meta_value, $prev_value = '' ) {
global $wpdb;
if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) {
return false;
}
$object_id = absint( $object_id );
if ( ! $object_id ) {
return false;
}
$table = _get_meta_table( $meta_type );
if ( ! $table ) {
return false;
}
$column = sanitize_key( $meta_type . '_id' );
$id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
// expected_slashed ($meta_key)
//$meta_key = wp_unslash($meta_key);
$passed_value = $meta_value;
//$meta_value = wp_unslash($meta_value);
$meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
/**
* Filter whether to update metadata of a specific type.
*
* The dynamic portion of the hook, $meta_type, refers to the meta
* object type (comment, post, or user). Returning a non-null value
* will effectively short-circuit the function.
*
* @param null|bool $check Whether to allow updating metadata for the given type.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value. Must be serializable if non-scalar.
* @param mixed $prev_value Optional. If specified, only update existing
* metadata entries with the specified value.
* Otherwise, update all entries.
*/
$check = apply_filters( "update_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $prev_value );
if ( null !== $check ) {
return (bool) $check;
}
// Compare existing value to new value if no prev value given and the key exists only once.
if ( empty( $prev_value ) ) {
$old_value = get_metadata( $meta_type, $object_id, $meta_key );
if ( count( $old_value ) == 1 ) {
if ( $old_value[0] === $meta_value ) {
return false;
}
}
}
if ( ! $meta_id = $wpdb->get_var( $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s AND $column = %d LIMIT 1", $meta_key, $object_id ) ) ) {
return fw_add_metadata( $meta_type, $object_id, $meta_key, $passed_value );
}
$_meta_value = $meta_value;
$meta_value = maybe_serialize( $meta_value );
$data = compact( 'meta_value' );
$where = array( $column => $object_id, 'meta_key' => $meta_key );
if ( ! empty( $prev_value ) ) {
$prev_value = maybe_serialize( $prev_value );
$where['meta_value'] = $prev_value;
}
/**
* Fires immediately before updating metadata of a specific type.
*
* The dynamic portion of the hook, $meta_type, refers to the meta
* object type (comment, post, or user).
*
* @param int $meta_id ID of the metadata entry to update.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
*/
do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
if ( 'post' == $meta_type ) {
/**
* Fires immediately before updating a post's metadata.
*
* @param int $meta_id ID of metadata entry to update.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
*/
do_action( 'update_postmeta', $meta_id, $object_id, $meta_key, $meta_value );
}
$result = $wpdb->update( $table, $data, $where );
if ( ! $result ) {
return false;
}
wp_cache_delete( $object_id, $meta_type . '_meta' );
/**
* Fires immediately after updating metadata of a specific type.
*
* The dynamic portion of the hook, $meta_type, refers to the meta
* object type (comment, post, or user).
*
* @param int $meta_id ID of updated metadata entry.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
*/
do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
if ( 'post' == $meta_type ) {
/**
* Fires immediately after updating a post's metadata.
*
* @param int $meta_id ID of updated metadata entry.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
*/
do_action( 'updated_postmeta', $meta_id, $object_id, $meta_key, $meta_value );
}
return true;
}
;
/**
* Delete metadata for the specified object.
*
* @uses $wpdb WordPress database object for queries.
*
* @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
* @param int $object_id ID of the object metadata is for
* @param string $meta_key Metadata key
* @param mixed $meta_value Optional. Metadata value. Must be serializable if non-scalar. If specified, only delete metadata entries
* with this value. Otherwise, delete all entries with the specified meta_key.
* @param bool $delete_all Optional, default is false. If true, delete matching metadata entries
* for all objects, ignoring the specified object_id. Otherwise, only delete matching
* metadata entries for the specified object_id.
*
* @return bool True on successful delete, false on failure.
*/
function fw_delete_metadata( $meta_type, $object_id, $meta_key, $meta_value = '', $delete_all = false ) {
/**
* @var WPDB $wpdb
*/
global $wpdb;
if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) && ! $delete_all ) {
return false;
}
$object_id = absint( $object_id );
if ( ! $object_id && ! $delete_all ) {
return false;
}
$table = _get_meta_table( $meta_type );
if ( ! $table ) {
return false;
}
$type_column = sanitize_key( $meta_type . '_id' );
$id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
// expected_slashed ($meta_key)
//$meta_key = wp_unslash($meta_key);
//$meta_value = wp_unslash($meta_value);
/**
* Filter whether to delete metadata of a specific type.
*
* The dynamic portion of the hook, $meta_type, refers to the meta
* object type (comment, post, or user). Returning a non-null value
* will effectively short-circuit the function.
*
* @param null|bool $delete Whether to allow metadata deletion of the given type.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value. Must be serializable if non-scalar.
* @param bool $delete_all Whether to delete the matching metadata entries
* for all objects, ignoring the specified $object_id.
* Default false.
*/
$check = apply_filters( "delete_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $delete_all );
if ( null !== $check ) {
return (bool) $check;
}
$_meta_value = $meta_value;
$meta_value = maybe_serialize( $meta_value );
$query = $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s", $meta_key );
if ( ! $delete_all ) {
$query .= $wpdb->prepare( " AND $type_column = %d", $object_id );
}
if ( $meta_value ) {
$query .= $wpdb->prepare( " AND meta_value = %s", $meta_value );
}
$meta_ids = $wpdb->get_col( $query );
if ( ! count( $meta_ids ) ) {
return false;
}
if ( $delete_all ) {
$object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s", $meta_key ) );
}
/**
* Fires immediately before deleting metadata of a specific type.
*
* The dynamic portion of the hook, $meta_type, refers to the meta
* object type (comment, post, or user).
*
* @param array $meta_ids An array of metadata entry IDs to delete.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
*/
do_action( "delete_{$meta_type}_meta", $meta_ids, $object_id, $meta_key, $_meta_value );
// Old-style action.
if ( 'post' == $meta_type ) {
/**
* Fires immediately before deleting metadata for a post.
*
* @param array $meta_ids An array of post metadata entry IDs to delete.
*/
do_action( 'delete_postmeta', $meta_ids );
}
$query = "DELETE FROM $table WHERE $id_column IN( " . implode( ',', $meta_ids ) . " )";
$count = $wpdb->query( $query );
if ( ! $count ) {
return false;
}
if ( $delete_all ) {
foreach ( (array) $object_ids as $o_id ) {
wp_cache_delete( $o_id, $meta_type . '_meta' );
}
} else {
wp_cache_delete( $object_id, $meta_type . '_meta' );
}
/**
* Fires immediately after deleting metadata of a specific type.
*
* The dynamic portion of the hook name, $meta_type, refers to the meta
* object type (comment, post, or user).
*
* @param array $meta_ids An array of deleted metadata entry IDs.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
*/
do_action( "deleted_{$meta_type}_meta", $meta_ids, $object_id, $meta_key, $_meta_value );
// Old-style action.
if ( 'post' == $meta_type ) {
/**
* Fires immediately after deleting metadata for a post.
*
* @param array $meta_ids An array of deleted post metadata entry IDs.
*/
do_action( 'deleted_postmeta', $meta_ids );
}
return true;
}
/**
* Add meta data field to a user.
*
* Post meta data is called "Custom Fields" on the Administration Screens.
*
* @param int $user_id User ID.
* @param string $meta_key Metadata name.
* @param mixed $meta_value Metadata value.
* @param bool $unique Optional, default is false. Whether the same key should not be added.
*
* @return int|bool Meta ID on success, false on failure.
*/
function fw_add_user_meta( $user_id, $meta_key, $meta_value, $unique = false ) {
return fw_add_metadata( 'user', $user_id, $meta_key, $meta_value, $unique );
}
/**
* Update user meta field based on user ID.
*
* Use the $prev_value parameter to differentiate between meta fields with the
* same key and user ID.
*
* If the meta field for the user does not exist, it will be added.
*
* @param int $user_id User ID.
* @param string $meta_key Metadata key.
* @param mixed $meta_value Metadata value.
* @param mixed $prev_value Optional. Previous value to check before removing.
*
* @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
*/
function fw_update_user_meta( $user_id, $meta_key, $meta_value, $prev_value = '' ) {
return fw_update_metadata( 'user', $user_id, $meta_key, $meta_value, $prev_value );
}
/**
* Remove metadata matching criteria from a user.
*
* You can match based on the key, or key and value. Removing based on key and
* value, will keep from removing duplicate metadata with the same key. It also
* allows removing all metadata matching key, if needed.
*
* @param int $user_id user ID
* @param string $meta_key Metadata name.
* @param mixed $meta_value Optional. Metadata value.
*
* @return bool True on success, false on failure.
*/
function fw_delete_user_meta( $user_id, $meta_key, $meta_value = '' ) {
return fw_delete_metadata( 'user', $user_id, $meta_key, $meta_value );
}
/**
* Add meta data field to a post.
*
* Post meta data is called "Custom Fields" on the Administration Screen.
*
* @param int $post_id Post ID.
* @param string $meta_key Metadata name.
* @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
* @param bool $unique Optional. Whether the same key should not be added.
* Default false.
*
* @return int|bool Meta ID on success, false on failure.
*/
function fw_add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) {
// Make sure meta is added to the post, not a revision. // fixme: why this is needed?
/*if ( $the_post = wp_is_post_revision( $post_id ) ) {
$post_id = $the_post;
}*/
return fw_add_metadata( 'post', $post_id, $meta_key, $meta_value, $unique );
}
/**
* Update post meta field based on post ID.
*
* Use the $prev_value parameter to differentiate between meta fields with the
* same key and post ID.
*
* If the meta field for the post does not exist, it will be added.
*
* @param int $post_id Post ID.
* @param string $meta_key Metadata key.
* @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
* @param mixed $prev_value Optional. Previous value to check before removing.
* Default empty.
*
* @return int|bool Meta ID if the key didn't exist, true on successful update,
* false on failure.
*/
function fw_update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) {
// Make sure meta is added to the post, not a revision. fixme: why this is needed?
/*if ( $the_post = wp_is_post_revision( $post_id ) ) {
$post_id = $the_post;
}*/
return fw_update_metadata( 'post', $post_id, $meta_key, $meta_value, $prev_value );
}
/**
* Remove metadata matching criteria from a post.
*
* You can match based on the key, or key and value. Removing based on key and
* value, will keep from removing duplicate metadata with the same key. It also
* allows removing all metadata matching key, if needed.
*
* @param int $post_id Post ID.
* @param string $meta_key Metadata name.
* @param mixed $meta_value Optional. Metadata value. Must be serializable if
* non-scalar. Default empty.
*
* @return bool True on success, false on failure.
*/
function fw_delete_post_meta( $post_id, $meta_key, $meta_value = '' ) {
// Make sure meta is added to the post, not a revision. // fixme: why this is needed?
/*if ( $the_post = wp_is_post_revision( $post_id ) ) {
$post_id = $the_post;
}*/
return delete_metadata( 'post', $post_id, $meta_key, $meta_value );
}
//
// Comment meta functions
//
/**
* Add meta data field to a comment.
*
* @param int $comment_id Comment ID.
* @param string $meta_key Metadata name.
* @param mixed $meta_value Metadata value.
* @param bool $unique Optional, default is false. Whether the same key should not be added.
*
* @return int|bool Meta ID on success, false on failure.
*/
function fw_add_comment_meta( $comment_id, $meta_key, $meta_value, $unique = false ) {
return fw_add_metadata( 'comment', $comment_id, $meta_key, $meta_value, $unique );
}
/**
* Update comment meta field based on comment ID.
*
* Use the $prev_value parameter to differentiate between meta fields with the
* same key and comment ID.
*
* If the meta field for the comment does not exist, it will be added.
*
* @param int $comment_id Comment ID.
* @param string $meta_key Metadata key.
* @param mixed $meta_value Metadata value.
* @param mixed $prev_value Optional. Previous value to check before removing.
*
* @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
*/
function fw_update_comment_meta( $comment_id, $meta_key, $meta_value, $prev_value = '' ) {
return fw_update_metadata( 'comment', $comment_id, $meta_key, $meta_value, $prev_value );
}
/**
* Remove metadata matching criteria from a comment.
*
* You can match based on the key, or key and value. Removing based on key and
* value, will keep from removing duplicate metadata with the same key. It also
* allows removing all metadata matching key, if needed.
*
* @param int $comment_id comment ID
* @param string $meta_key Metadata name.
* @param mixed $meta_value Optional. Metadata value.
*
* @return bool True on success, false on failure.
*/
function fw_delete_comment_meta( $comment_id, $meta_key, $meta_value = '' ) {
return fw_delete_metadata( 'comment', $comment_id, $meta_key, $meta_value );
}
//
// Term meta functions
//http://core.trac.wordpress.org/ticket/10142
//
/**
* Add meta data field to a term.
*
* @param int $term_id Post ID.
* @param string $key Metadata name.
* @param mixed $value Metadata value.
* @param bool $unique Optional, default is false. Whether the same key should not be added.
* @return bool False for failure. True for success.
*/
function fw_add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) {
return fw_add_metadata( 'fw_term', $term_id, $meta_key, $meta_value, $unique );
}
/**
* Remove metadata matching criteria from a term.
*
* You can match based on the key, or key and value. Removing based on key and
* value, will keep from removing duplicate metadata with the same key. It also
* allows removing all metadata matching key, if needed.
*
* @param int $term_id term ID
* @param string $meta_key Metadata name.
* @param mixed $meta_value Optional. Metadata value.
*
* @return bool False for failure. True for success.
*/
function fw_delete_term_meta( $term_id, $meta_key, $meta_value = '' ) {
return fw_delete_metadata( 'fw_term', $term_id, $meta_key, $meta_value );
}
/**
* Retrieve term meta field for a term.
*
* @param int $term_id Term ID.
* @param string $key The meta key to retrieve.
* @param bool $single Whether to return a single value.
*
* @return mixed Will be an array if $single is false. Will be value of meta data field if $single
* is true.
*/
function fw_get_term_meta( $term_id, $key, $single = false ) {
return get_metadata( 'fw_term', $term_id, $key, $single );
}
/**
* Update term meta field based on term ID.
*
* Use the $prev_value parameter to differentiate between meta fields with the
* same key and term ID.
*
* If the meta field for the term does not exist, it will be added.
*
* @param int $term_id Term ID.
* @param string $key Metadata key.
* @param mixed $value Metadata value.
* @param mixed $prev_value Optional. Previous value to check before removing.
*
* @return bool False on failure, true if success.
*/
function fw_update_term_meta( $term_id, $meta_key, $meta_value, $prev_value = '' ) {
return fw_update_metadata( 'fw_term', $term_id, $meta_key, $meta_value, $prev_value );
}

View File

@@ -0,0 +1,89 @@
<?php if ( ! defined( 'FW' ) ) die( 'Forbidden' );
/**
* Give users the possibility to register a type safely
* Instead of doing apply_filters('my_types', $types) where someone can mess your data
* with this class you do do_action('register_my_types', $types)
* and users will be able only to $types->register(new Allowed_Type_Class())
* @since 2.4.10
*/
abstract class FW_Type_Register {
/**
* Check if the type is instance of the required class (or other requirements)
* @param FW_Type $type
* @return bool|WP_Error
*/
abstract protected function validate_type(FW_Type $type);
/**
* @var FW_Type[]
*/
protected $types = array();
/**
* Only these access keys will be able to access the registered types
* @var array {'key': true}
*/
protected $access_keys = array();
final public function __construct($access_keys) {
{
if (is_string($access_keys)) {
$access_keys = array(
$access_keys => true,
);
} elseif (!is_array($access_keys)) {
trigger_error('Invalid access key', E_USER_ERROR);
}
$this->access_keys = $access_keys;
}
}
public function register(FW_Type $type) {
if (isset($this->task_types[$type->get_type()])) {
throw new Exception('Type '. $type->get_type() .' already registered');
} elseif (
is_wp_error($validation_result = $this->validate_type($type))
||
!$validation_result
) {
throw new Exception(
'Invalid type '. $type->get_type()
.(is_wp_error($validation_result) ? ': '. $validation_result->get_error_message() : '')
);
}
$this->types[$type->get_type()] = $type;
}
/**
* @param FW_Access_Key $access_key
*
* @return FW_Type[]
* @internal
*/
public function _get_types(FW_Access_Key $access_key) {
if (!isset($this->access_keys[$access_key->get_key()])) {
trigger_error('Method call denied', E_USER_ERROR);
}
return $this->types;
}
/**
* @param FW_Access_Key $access_key
* @param $type
*
* @return FW_Type|null
* @internal
* @since 2.5.12
*/
public function _get_type(FW_Access_Key $access_key, $type) {
if (!isset($this->access_keys[$access_key->get_key()])) {
trigger_error('Method call denied', E_USER_ERROR);
}
return isset($this->types[$type]) ? $this->types[$type] : null;
}
}

View File

@@ -0,0 +1,11 @@
<?php if ( ! defined( 'FW' ) ) die( 'Forbidden' );
/**
* @since 2.4.10
*/
abstract class FW_Type {
/**
* @return string Unique type
*/
abstract public function get_type();
}