web/wp-content/plugins/acf-extended/includes/module.php (550 lines of code) (raw):

<?php if(!defined('ABSPATH')){ exit; } if(!class_exists('acfe_module')): class acfe_module{ // vars public $name = '', $plural = '', $post_type = '', $args = array(), $setting = '', $settings = '', $view = '', $register = '', $export_tool = '', $import_tool = '', $export_actions = array('php', 'json'), $export_files = array(), $messages = array(), $item = array(), $validate = array(), $columns = array(), $alias = array(), $l10n = array(), $modified = null; /** * construct */ function __construct(){ // setup $this->initialize(); // register stores // todo move 'local-any-post_type' store outside module acf_register_store("local-{$this->post_type}"); acf_register_store("local-any-{$this->post_type}"); acf_register_store($this->post_type)->prop('multisite', true); $this->add_module_filter('acfe/module/register_field_groups', array($this, 'register_field_groups'), 9); $this->add_module_filter('acfe/module/register_items', array($this, 'register_items'), 9); $this->add_module_filter('acfe/module/register_item_args', array($this, 'register_item_args'), 9); $this->add_module_action('acfe/module/register_item', array($this, 'register_item'), 9); $this->add_module_action('acfe/module/load_post', array($this, 'load_post'), 9); $this->add_module_action('acfe/module/load_posts', array($this, 'load_posts'), 9); $this->add_module_filter('acfe/module/edit_columns', array($this, 'edit_columns'), 9); $this->add_module_action('acfe/module/edit_columns_html', array($this, 'edit_columns_html'), 9, 2); $this->add_module_filter('acfe/module/validate_save_item', array($this, 'validate_save_item'), 9); $this->add_module_filter('acfe/module/prepare_load_item', array($this, 'prepare_load_item'), 9); $this->add_module_filter('acfe/module/prepare_save_item', array($this, 'prepare_save_item'), 9); $this->add_module_action('acfe/module/updated_item', array($this, 'updated_item'), 9); $this->add_module_action('acfe/module/trashed_item', array($this, 'trashed_item'), 9); $this->add_module_action('acfe/module/untrashed_item', array($this, 'untrashed_item'), 9); $this->add_module_action('acfe/module/deleted_item', array($this, 'deleted_item'), 9); $this->add_module_action('acfe/module/imported_item', array($this, 'imported_item'), 9); $this->add_module_filter('acfe/module/validate_item', array($this, 'validate_item'), 9); $this->add_module_filter('acfe/module/load_item', array($this, 'load_item'), 9); $this->add_module_filter('acfe/module/load_items', array($this, 'load_items'), 9); $this->add_action('acfe/do_reset', array($this, 'reset')); // add to reserved post types acfe_append_setting('reserved_post_types', $this->post_type); } /** * initialize */ function initialize(){ // ... } /** * is_active * @return bool */ function is_active(){ if(!$this->setting){ return true; } return (bool) acfe_get_setting($this->setting); } /** * get_field_groups * * @return mixed */ function get_field_groups(){ return $this->apply_module_filters('acfe/module/register_field_groups', array()); } /** * validate_item * * @param $item * * @return array|mixed */ function validate_item($item = array()){ // already valid if(is_array($item) && !empty($item['_valid'])){ return $item; } // convert $item['ID'] = (int) acf_maybe_get($item, 'ID', 0); $item['active'] = (bool) acf_maybe_get($item, 'active', true); $item['_valid'] = true; // default item $defaults = wp_parse_args($this->item, array( 'ID' => 0, 'name' => '', 'label' => '', )); // parse defaults $item = acfe_parse_args_r($item, $defaults); // process alias foreach($this->alias as $k => $alias){ if(!empty($item[ $alias ])){ // set 'page_title' = 'label' $item[ $k ] = $item[ $alias ]; } } // filters $item = $this->apply_module_filters('acfe/module/validate_item', $item); return $item; } /** * update_item * * @param $item * * @return array */ function update_item($item){ // make sure name is unique in db $name = wp_unique_post_slug($item['name'], $item['ID'], (!empty($item['active']) ? 'publish' : 'acf-disabled'), $this->post_type, 0); if($item['name'] !== $name){ $item['name'] = $name; acf_enable_filter('acfe/module/update_unique_name'); } // validate $item = $this->validate_item($item); // prepare item $item = wp_unslash($item); $item = acfe_parse_types($item); // todo: do not parse types on 'values' => array() // modified as global var $this->modified = acf_extract_var($item, 'modified'); // cleanup keys $export = $this->prepare_item_for_export($item); // array of data $save = array( 'ID' => $item['ID'], 'post_status' => $item['active'] ? 'publish' : 'acf-disabled', 'post_type' => $this->post_type, 'post_title' => $item['label'], 'post_name' => $item['name'], 'post_content' => maybe_serialize($export), 'comment_status' => 'closed', 'ping_status' => 'closed', ); // bypass post modified add_filter('wp_insert_post_data', array($this, 'bypass_post_modified'), 1, 2); // remove filter to avoid serialized data corruption remove_filter('content_save_pre', 'wp_targeted_link_rel'); // slash $save = wp_slash($save); // update or insert if($item['ID']){ wp_update_post($save); }else{ $item['ID'] = wp_insert_post($save); } // remove bypass post modified remove_filter('wp_insert_post_data', array($this, 'bypass_post_modified'), 1); // flush cache $this->flush_cache($item); // actions $this->do_module_action('acfe/module/updated_item', $item); // delete _wp_old_slug meta delete_post_meta($item['ID'], '_wp_old_slug'); // return return $item; } /** * bypass_post_modified * * bypass post_modified automatically set to current date by wp_update_post * https://brogramo.com/how-to-update-a-wordpress-post-without-updating-the-modified-date-using-wp_update_post/ * * @param $data * @param $array * * @return mixed */ function bypass_post_modified($data, $array){ if(!isset($data['post_modified'], $data['post_modified_gmt']) || !$this->modified){ return $data; } $data['post_modified'] = wp_date('Y-m-d H:i:s', $this->modified); $data['post_modified_gmt'] = get_gmt_from_date($data['post_modified']); return $data; } /** * trash_item * * @param $id * * @return bool */ function trash_item($id){ // disable filters to get from db acf_disable_filters(); // get $item = $this->get_item($id); // bail early if(!$item || !$item['ID']){ return false; } // trash wp_trash_post($item['ID']); // flush $this->flush_cache($item); // actions $this->do_module_action('acfe/module/trashed_item', $item); // return return true; } /** * untrash_item * * @param $id * * @return bool */ function untrash_item($id){ // disable filters to get from db acf_disable_filters(); // get raw item (to avoid validate_item) $item = $this->get_raw_item($id); // bail early if(!$item || !$item['ID']){ return false; } // untrash wp_untrash_post($item['ID']); // update item (new status etc...) $this->update_item($item); // already flushed in update_item // $this->flush_cache($item); // actions $this->do_module_action('acfe/module/untrashed_item', $item); // return return true; } /** * delete_item * * @param $id * * @return bool */ function delete_item($id){ // disable filters to get from db acf_disable_filters(); // get $item = $this->get_item($id); // bail early if(!$item || !$item['ID']){ return false; } // delete post wp_delete_post($item['ID'], true); // flush $this->flush_cache($item); // actions $this->do_module_action('acfe/module/deleted_item', $item); // return return true; } /** * import_item * * @param $item * * @return array */ function import_item($item){ // disable filters to ensure data is not modified by local, clone, etc. $filters = acf_disable_filters(); // validate item (ensures all settings exist). $item = $this->validate_item($item); // prepare item for import (modifies settings). $item = $this->prepare_item_for_import($item); // save item $item = $this->update_item($item); // enable filters again. acf_enable_filters($filters); // actions $this->do_module_action('acfe/module/imported_item', $item); // return return $item; } /** * duplicate_item * * @param $id * @param $new_post_id * * @return array|false */ function duplicate_item($id = 0, $new_post_id = 0){ // get raw item $item = $this->get_raw_item($id); // bail early if item was not found if(!$item || !$item['ID']){ return false; } // update attributes $item['ID'] = $new_post_id; // Add (copy) to title when apropriate if(!$new_post_id){ $item['label'] .= ' (' . __('copy', 'acf') . ')'; } // save item $item = $this->update_item($item); // actions $this->do_module_action('acfe/module/duplicated_item', $item); // return return $item; } /** * get_item * * @param $id * * @return array|false|mixed|null */ function get_item($id = 0){ // allow wp object if(is_object($id)){ $id = $id->ID; } // check store $store = acf_get_store($this->post_type); if($store->has($id)){ return $store->get($id); } // check local if($this->is_local_item($id)){ $item = $this->get_local_item($id); // Then check db }else{ $item = $this->get_raw_item($id); } // bail early if(!$item){ return false; } // validate $item = $this->validate_item($item); // filters $item = $this->apply_module_filters('acfe/module/load_item', $item); // store $store->set($item['name'], $item); $store->alias($item['name'], $item['ID']); // return return $item; } /** * get_local_item * * @param $name * * @return array|false|mixed */ function get_local_item($name = ''){ $item = acf_get_local_store($this->post_type)->get($name); if(!$item){ return false; } // validate item $item = $this->validate_item($item); return $item; } /** * get_raw_item * * @param $id * * @return array|false|mixed */ function get_raw_item($id = 0){ // get raw $post = $this->get_item_post($id); if(!$post){ return false; } // bail early if incorrect post type if($post->post_type !== $this->post_type){ return false; } // unserialize post content $item = acf_get_array(maybe_unserialize($post->post_content)); // prepend id $item = wp_parse_args($item, array( 'ID' => $post->ID, 'name' => $post->post_name, 'label' => $post->post_title, )); // validate item $item = $this->validate_item($item); // return return $item; } /** * get_item_post * * @param $id * * @return array|false|WP_Post|null */ function get_item_post($id = 0){ // get post if numeric if(is_numeric($id)){ return get_post($id); // search posts if string }elseif(is_string($id)){ // try cache $cache_key = acf_cache_key("{$this->post_type}_post:name:$id"); $post_id = wp_cache_get($cache_key, 'acfe'); if($post_id === false){ // query posts $posts = get_posts(array( 'name' => $id, 'posts_per_page' => 1, 'post_type' => $this->post_type, 'post_status' => array('publish', 'acf-disabled', 'trash'), 'orderby' => 'menu_order title', 'order' => 'ASC', 'suppress_filters' => false, 'cache_results' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, )); // update post with non false value $post_id = $posts ? $posts[0]->ID : 0; // update cache wp_cache_set($cache_key, $post_id, 'acfe'); } // check psot id and return psot when possible if($post_id){ return get_post($post_id); } } // return return false; } /** * get_items * * retrieve raw + local items * * @return mixed */ function get_items(){ // vars $items = array(); // raw items foreach($this->get_raw_items() as $raw_item){ $items[] = $this->get_item($raw_item['ID']); } // check local filter $is_local = acf_is_filter_enabled('local'); // enable filter if(!$is_local){ acf_enable_filter('local'); } // get local items $local = $this->get_local_items(); if($local){ // generate map of 'index' => 'name' data. $map = wp_list_pluck($items, 'name'); // loop over items and update/append local foreach($local as $item){ // update $i = array_search($item['name'], $map); if($i !== false){ unset($item['ID']); $items[ $i ] = array_merge($items[ $i ], $item); // append }else{ $items[] = $this->get_item($item['name']); } } // sort list via name $items = wp_list_sort($items, array( 'name' => 'ASC', )); } // disable filter if(!$is_local){ acf_disable_filter('local'); } // filters $items = $this->apply_module_filters('acfe/module/load_items', $items); // return return $items; } /** * get_local_items * * @return array */ function get_local_items(){ // vars $items = array(); $local_items = acf_get_local_store($this->post_type)->get(); foreach($local_items as $local_item){ $items[] = $this->validate_item($local_item); } return $items; } /** * get_raw_items * * @return array */ function get_raw_items(){ // try cache $cache_key = acf_cache_key($this->post_type); $post_ids = wp_cache_get($cache_key, 'acfe'); if($post_ids === false){ // query $posts = get_posts(array( 'posts_per_page' => -1, 'post_type' => $this->post_type, 'orderby' => 'menu_order title', 'order' => 'ASC', 'suppress_filters' => false, // Allow WPML to modify the query 'cache_results' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'post_status' => array('publish', 'acf-disabled'), )); // update post ids with non false values $post_ids = array(); foreach($posts as $post){ $post_ids[] = $post->ID; } // update cache wp_cache_set($cache_key, $post_ids, 'acfe'); } // loop and get raw $items = array(); foreach($post_ids as $post_id){ $raw_item = $this->get_raw_item($post_id); if($raw_item){ $items[] = $raw_item; } } // return return $items; } /** * flush_cache * * @param $item */ function flush_cache($item){ // delete stored data. acf_get_store($this->post_type)->remove($item['name']); // flush cached post_id for this item name wp_cache_delete(acf_cache_key("{$this->post_type}_post:name:{$item['name']}"), 'acfe'); // flush cached array of post_ids for collection of items wp_cache_delete(acf_cache_key($this->post_type), 'acfe'); } /** * prepare_item_for_export * * @param $item * * @return mixed */ function prepare_item_for_export($item = array()){ // todo: different export for acf function such as acf_register_block_type() with no custom args like acfe_autosync // remove args acf_extract_vars($item, array('ID', 'local', 'local_file', '_valid')); // remove alias keys acf_extract_vars($item, $this->alias); // filters $item = $this->apply_module_filters('acfe/module/prepare_item_for_export', $item); return $item; } /** * prepare_item_for_import * * @param $item * * @return mixed */ function prepare_item_for_import($item){ // process alias foreach($this->alias as $k => $alias){ if(!empty($item[ $k ])){ // set 'label' = 'page_title' $item[ $alias ] = $item[ $k ]; } } // filters $item = $this->apply_module_filters('acfe/module/prepare_item_for_import', $item); return $item; } /** * translate_item * * @param $item * * @return mixed */ function translate_item($item = array()){ foreach($this->l10n as $k){ acfe_array_set($item, $k, acf_translate(acfe_array_get($item, $k))); } return $item; } /** * is_local_item * * @param $name * * @return bool */ function is_local_item($name = ''){ return acf_get_local_store($this->post_type)->has($name); } /** * add_local_item * * @param $item */ function add_local_item($item){ // apply default properties $item = wp_parse_args($item, array( 'name' => '', 'label' => '', 'local' => 'php', )); // append local file for php if($item['local'] === 'php'){ // get php local file path $backtrace = debug_backtrace(); // append php local file if(isset($backtrace[1]['file'])){ $item['local_file'] = wp_normalize_path($backtrace[1]['file']); } } // prepare item keys (alias etc...) $item = $this->prepare_item_for_import($item); // generate name if not provided if(!$item['name']){ $item['name'] = acf_slugify($item['label']); } // add item to store if it doesn't exist if(!$this->is_local_item($item['name'])){ acf_get_local_store($this->post_type)->set($item['name'], $item); } // action $this->do_module_action('acfe/module/add_local_item', $item); } /** * export_code * * @param $code * @param $args * * @return mixed */ function export_code($code, $args){ return ''; } /** * export_local_code * * @param $code * @param $args * * @return mixed */ function export_local_code($code, $args){ return $this->export_code($code, $args); } /** * get_export_tool * * @return string */ function get_export_tool(){ return !empty($this->export_tool) ? $this->export_tool : "acfe_module_{$this->name}_export"; } /** * get_import_tool * * @return string */ function get_import_tool(){ return !empty($this->import_tool) ? $this->import_tool : "acfe_module_{$this->name}_import"; } /** * get_export_url * * @param $action * @param $item * * @return string|void */ function get_export_url($action, $item){ $name = acf_maybe_get($item, 'name', $item); $tool = $this->get_export_tool(); return admin_url("edit.php?post_type=acf-field-group&page=acf-tools&tool={$tool}&action={$action}&keys={$name}"); } /** * get_export_link * * @param $action * @param $item * @param $text * * @return string */ function get_export_link($action, $item, $text = ''){ $name = acf_maybe_get($item, 'name', $item); $text = !empty($text) ? $text : $this->get_export_action_label($action); return '<a href="' . $this->get_export_url($action, $name) . '">' . $text . '</a>'; } /** * get_export_links * * @param $item * * @return array */ function get_export_links($item){ $links = array(); foreach($this->export_actions as $action){ $links[] = $this->get_export_link($action, $item['name']); } return $links; } /** * get_export_action_label * * @param $action * * @return string */ function get_export_action_label($action){ return $action === 'php' ? 'PHP' : 'Json'; } /** * get_label * * @param $type * * @return mixed|null */ function get_label($type = 'singular_name'){ $labels = acfe_maybe_get($this->args, 'labels'); if(!empty($labels)){ return acf_maybe_get($labels, $type); } return acfe_maybe_get($this->args, 'label'); } /** * get_message * * @param $name * * @return mixed|null */ function get_message($name){ return acf_maybe_get($this->messages, $name); } /** * reset */ function reset(){ // disable sync to avoid generating files acfe_update_setting('php', false); acfe_update_setting('json', false); // get raw items $items = $this->get_raw_items(); if(!empty($items)){ foreach($items as $item){ // update db local // do not use module->update_item() to avoid changing post date $this->do_module_action('acfe/module/updated_item', $item); } // Log $message = '[ACF Extended] ' . __('Reset', 'acfe') . ': ' . $this->get_label('name'); acf_log($message); } } /** * do_action * * @param $tag * @param ...$args */ function do_action($tag, ...$args){ $args[] = $this; do_action_ref_array($tag, $args); } /** * apply_filters * * @param $tag * @param ...$args * * @return mixed */ function apply_filters($tag, ...$args){ $args[] = $this; return apply_filters_ref_array($tag, $args); } /** * do_module_action * * @param $tag * @param ...$args */ function do_module_action($tag, ...$args){ $args[] = $this; do_action_ref_array("{$tag}/module={$this->name}", $args); do_action_ref_array($tag, $args); } /** * apply_module_filters * * @param $tag * @param ...$args * * @return mixed */ function apply_module_filters($tag, ...$args){ $args[] = $this; $args[0] = apply_filters_ref_array("{$tag}/module={$this->name}", $args); $args[0] = apply_filters_ref_array($tag, $args); return $args[0]; } /** * add_action * * @param $tag * @param $function_to_add * @param $priority * @param $accepted_args */ function add_action($tag = '', $function_to_add = '', $priority = 10, $accepted_args = 1){ if(is_callable($function_to_add)){ add_action($tag, $function_to_add, $priority, $accepted_args); } } /** * add_filter * * @param $tag * @param $function_to_add * @param $priority * @param $accepted_args */ function add_filter($tag = '', $function_to_add = '', $priority = 10, $accepted_args = 1){ if(is_callable($function_to_add)){ add_filter($tag, $function_to_add, $priority, $accepted_args); } } /** * add_module_action * * @param $tag * @param $function_to_add * @param $priority * @param $accepted_args */ function add_module_action($tag = '', $function_to_add = '', $priority = 10, $accepted_args = 1){ $tag .= "/module={$this->name}"; $this->add_action($tag, $function_to_add, $priority, $accepted_args); } /** * add_module_filter * * @param $tag * @param $function_to_add * @param $priority * @param $accepted_args */ function add_module_filter($tag = '', $function_to_add = '', $priority = 10, $accepted_args = 1){ $tag .= "/module={$this->name}"; $this->add_filter($tag, $function_to_add, $priority, $accepted_args); } } endif; // register store acf_register_store('acfe-modules'); /** * acfe_register_module * * @param $class * * @return bool */ function acfe_register_module($class){ // instantiate $module = new $class(); // add to store acf_get_store('acfe-modules')->set($module->name, $module); // return return true; } /** * acfe_get_modules * @return array|mixed|null */ function acfe_get_modules(){ return acf_get_store('acfe-modules')->get(); } /** * acfe_get_module * * @param $module * * @return acfe_module|array|mixed|null */ function acfe_get_module($module){ if($module instanceof acfe_module){ return $module; } return acf_get_store('acfe-modules')->get($module); } /** * acfe_query_module * * @param array $query * * @return false|mixed */ function acfe_query_module($query = array()){ $modules = acfe_query_modules($query); return current($modules); } /** * acfe_query_modules * * @param $query * * @return false|mixed */ function acfe_query_modules($query = array()){ return acf_get_store('acfe-modules')->query($query); } /** * acfe_get_module_by_post_type * * @param $post_type * * @return false|mixed */ function acfe_get_module_by_post_type($post_type){ return acfe_query_module(array('post_type' => $post_type)); } /** * acfe_get_module_by_item * * @param $id * * @return false|mixed */ function acfe_get_module_by_item($id){ // check array/object if(is_array($id) || is_object($id)){ $id = acfe_maybe_get($id, 'ID'); } $id = absint($id); if(!$id){ return false; } return acfe_get_module_by_post_type(get_post_type($id)); } /** * acfe_is_module_v2_item * * @param $post_id * * @return bool */ function acfe_is_module_v2_item($post_id){ $post = get_post($post_id); $meta_name = false; // validate post if(!$post){ return false; } // define meta name switch($post->post_type){ case 'acfe-dbt': { $meta_name = 'name'; break; } case 'acfe-form': { $meta_name = 'acfe_form_name'; break; } case 'acfe-dop': { $meta_name = 'menu_slug'; break; } case 'acfe-dpt': { $meta_name = 'acfe_dpt_name'; break; } case 'acfe-dt': { $meta_name = 'acfe_dt_name'; break; } case 'acfe-template': { $meta_name = 'acfe_template_active'; break; } } // validate meta name if(!$meta_name){ return false; } // get post meta $post_meta = get_post_meta($post_id, $meta_name, true); // validate old meta name // empty and not 0 if(acf_is_empty($post_meta)){ return false; } // get post content $post_content = maybe_unserialize($post->post_content); $post_content = acf_get_array($post_content); // post content already set if($post_content){ return false; } // is module v2 item return true; }