/wp-content/plugins/broken-link-checker/modules/containers/custom_field.php
PHP | 580 lines | 295 code | 63 blank | 222 comment | 37 complexity | a4181b72e4b12be5b27c0dec3f9eb523 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, AGPL-1.0
- <?php
- /*
- Plugin Name: Custom fields
- Description: Container module for post metadata.
- Version: 1.0
- Author: Janis Elsts
- ModuleID: custom_field
- ModuleCategory: container
- ModuleClassName: blcPostMetaManager
- */
- //Note : If it ever becomes necessary to check metadata on objects other than posts, it will
- //be fairly easy to extract a more general metadata container class from blcPostMeta.
- /**
- * blcPostMeta - A link container class for post metadata (AKA custom fields).
- *
- * Due to the way metadata works, this container differs significantly from other containers :
- * - container_field is equal to meta name, and container_id holds the ID of the post.
- * - There is one synch. record per post that determines the synch. state of all metadata fields of that post.
- * - Unlinking simply deletes the meta entry in question without involving the parser.
- * - The list of parse-able $fields is not fixed. Instead, it's initialized based on the
- * custom field list defined in Settings -> Link Checker.
- * - The $wrapped_object is an array (and isn't really used for anything).
- * - update_wrapped_object() does nothing.
- *
- * @package Broken Link Checker
- * @access public
- */
- class blcPostMeta extends blcContainer {
-
- var $meta_type = 'post';
-
- /**
- * Retrieve all metadata fields of the post associated with this container.
- * The results are cached in the internal $wrapped_object variable.
- *
- * @param bool $ensure_consistency
- * @return object The wrapped object.
- */
- function get_wrapped_object($ensure_consistency = false){
- if ( is_null($this->wrapped_object) || $ensure_consistency ) {
- $this->wrapped_object = get_metadata($this->meta_type, $this->container_id);
- }
- return $this->wrapped_object;
- }
-
- function update_wrapped_object(){
- trigger_error('Function blcPostMeta::update_wrapped_object() does nothing and should not be used.', E_USER_WARNING);
- }
-
- /**
- * Get the value of the specified metadata field of the object wrapped by this container.
- *
- * @access protected
- *
- * @param string $field Field name. If omitted, the value of the default field will be returned.
- * @return array
- */
- function get_field($field = ''){
- return get_metadata($this->meta_type, $this->container_id, $field);
- }
-
- /**
- * Update the value of the specified metadata field of the object wrapped by this container.
- *
- * @access protected
- *
- * @param string $field Meta name.
- * @param string $new_value New meta value.
- * @param string $old_value old meta value.
- * @return bool|WP_Error True on success, an error object if something went wrong.
- */
- function update_field($field, $new_value, $old_value = ''){
- $rez = update_metadata($this->meta_type, $this->container_id, $field, $new_value, $old_value);
- if ( $rez ){
- return true;
- } else {
- return new WP_Error(
- 'metadata_update_failed',
- sprintf(
- __("Failed to update the meta field '%s' on %s [%d]", 'broken-link-checker'),
- $field,
- $this->meta_type,
- $this->container_id
- )
- );
- }
- }
-
- /**
- * "Unlink"-ing a custom fields removes all metadata fields that contain the specified URL.
- *
- * @param string $field_name
- * @param blcParser $parser_type
- * @param string $url
- * @param string $raw_url
- * @return bool|WP_Error True on success, or an error object if something went wrong.
- */
- function unlink($field_name, $parser, $url, $raw_url =''){
- $rez = delete_metadata($this->meta_type, $this->container_id, $field_name, $raw_url);
- if ( $rez ){
- return true;
- } else {
- return new WP_Error(
- 'metadata_delete_failed',
- sprintf(
- __("Failed to delete the meta field '%s' on %s [%d]", 'broken-link-checker'),
- $field_name,
- $this->meta_type,
- $this->container_id
- )
- );
- }
- }
-
- /**
- * Change a meta field containing the specified URL to a new URL.
- *
- * @param string $field_name Meta name
- * @param blcParser $parser
- * @param string $new_url New URL.
- * @param string $old_url
- * @param string $old_raw_url Old meta value.
- * @return string|WP_Error The new value of raw_url on success, or an error object if something went wrong.
- */
- function edit_link($field_name, $parser, $new_url, $old_url = '', $old_raw_url = ''){
- /*
- FB::log(sprintf(
- 'Editing %s[%d]:%s - %s to %s',
- $this->container_type,
- $this->container_id,
- $field_name,
- $old_url,
- $new_url
- ));
- */
-
- if ( empty($old_raw_url) ){
- $old_raw_url = $old_url;
- }
-
- //Get the current values of the field that needs to be edited.
- //The default metadata parser ignores them, but we're still going
- //to set this argument to a valid value in case someone writes a
- //custom meta parser that needs it.
- $old_value = $this->get_field($field_name);
-
- //Get the new field value (a string).
- $edit_result = $parser->edit($old_value, $new_url, $old_url, $old_raw_url);
- if ( is_wp_error($edit_result) ){
- return $edit_result;
- }
-
- //Update the field with the new value returned by the parser.
- //Notice how $old_raw_url is usead instead of $old_value. $old_raw_url contains the entire old
- //value of the metadata field (see blcMetadataParser::parse()) and thus can be used to
- //differentiate between multiple meta fields with identical names.
- $update_result = $this->update_field( $field_name, $edit_result['content'], $old_raw_url );
- if ( is_wp_error($update_result) ){
- return $update_result;
- }
-
- //Return the new "raw" URL.
- return $edit_result['raw_url'];
- }
-
- /**
- * Get the default link text to use for links found in a specific container field.
- *
- * @param string $field
- * @return string
- */
- function default_link_text($field = ''){
- //Just use the field name. There's no way to know how the links inside custom fields are
- //used, so no way to know the "real" link text. Displaying the field name at least gives
- //the user a clue where to look if they want to find/modify the field.
- return $field;
- }
-
- function ui_get_source($container_field = '', $context = 'display'){
- $post_html = sprintf(
- '<a class="row-title" href="%s" title="%s">%s</a>',
- esc_url($this->get_edit_url()),
- esc_attr(__('Edit this post')),
- get_the_title($this->container_id)
- );
-
- return $post_html;
- }
-
- function ui_get_action_links($container_field){
- $actions = array();
- if ( current_user_can('edit_post', $this->container_id) ) {
- $actions['edit'] = '<span class="edit"><a href="' . $this->get_edit_url() . '" title="' . esc_attr(__('Edit this item')) . '">' . __('Edit') . '</a>';
-
- if ( $this->current_user_can_delete() ){
- if ( $this->can_be_trashed() ) {
- $actions['trash'] = sprintf(
- "<span><a class='submitdelete' title='%s' href='%s'>%s</a>",
- esc_attr(__('Move this item to the Trash')),
- get_delete_post_link($this->container_id, '', false),
- __('Trash')
- );
- } else {
- $actions['delete'] = sprintf(
- "<span><a class='submitdelete' title='%s' href='%s'>%s</a>",
- esc_attr(__('Delete this item permanently')),
- get_delete_post_link($this->container_id, '', true),
- __('Delete')
- );
- }
- }
- }
- $actions['view'] = '<span class="view"><a href="' . esc_url(get_permalink($this->container_id)) . '" title="' . esc_attr(sprintf(__('View "%s"', 'broken-link-checker'), get_the_title($this->container_id))) . '" rel="permalink">' . __('View') . '</a>';
-
- return $actions;
- }
-
- /**
- * Get edit URL for this container. Returns the URL of the Dashboard page where the item
- * associated with this container can be edited.
- *
- * @access protected
- *
- * @return string
- */
- function get_edit_url(){
- /*
- The below is a near-exact copy of the get_post_edit_link() function.
- Unfortunately we can't just call that function because it has a hardcoded
- caps-check which fails when called from the email notification script
- executed by Cron.
- */
-
- if ( !($post = &get_post( $this->container_id )) ){
- return '';
- }
-
- $context = 'display';
-
- //WP 3.0
- if ( 'display' == $context )
- $action = '&action=edit';
- else
- $action = '&action=edit';
-
- $post_type_object = get_post_type_object( $post->post_type );
- if ( !$post_type_object ){
- return '';
- }
-
- return apply_filters( 'get_edit_post_link', admin_url( sprintf($post_type_object->_edit_link . $action, $post->ID) ), $post->ID, $context );
- }
-
- /**
- * Get the base URL of the container. For custom fields, the base URL is the permalink of
- * the post that the field is attached to.
- *
- * @return string
- */
- function base_url(){
- return get_permalink($this->container_id);
- }
-
- /**
- * Delete or trash the post corresponding to this container. If trash is enabled,
- * will always move the post to the trash instead of deleting.
- *
- * @return bool|WP_error
- */
- function delete_wrapped_object(){
- if ( EMPTY_TRASH_DAYS ){
- return $this->trash_wrapped_object();
- } else {
- if ( wp_delete_post($this->container_id) ){
- return true;
- } else {
- return new WP_Error(
- 'delete_failed',
- sprintf(
- __('Failed to delete post "%s" (%d)', 'broken-link-checker'),
- get_the_title($this->container_id),
- $this->container_id
- )
- );
- }
- }
- }
-
- /**
- * Move the post corresponding to this custom field to the Trash.
- *
- * @return bool|WP_Error
- */
- function trash_wrapped_object(){
- if ( !EMPTY_TRASH_DAYS ){
- return new WP_Error(
- 'trash_disabled',
- sprintf(
- __('Can\'t move post "%s" (%d) to the trash because the trash feature is disabled', 'broken-link-checker'),
- get_the_title($this->container_id),
- $this->container_id
- )
- );
- }
-
- $post = &get_post($this->container_id);
- if ( $post->post_status == 'trash' ){
- //Prevent conflicts between post and custom field containers trying to trash the same post.
- return true;
- }
-
- if ( wp_trash_post($this->container_id) ){
- return true;
- } else {
- return new WP_Error(
- 'trash_failed',
- sprintf(
- __('Failed to move post "%s" (%d) to the trash', 'broken-link-checker'),
- get_the_title($this->container_id),
- $this->container_id
- )
- );
- }
- }
-
- function current_user_can_delete(){
- $post = &get_post($this->container_id);
- $post_type_object = get_post_type_object($post->post_type);
- return current_user_can( $post_type_object->cap->delete_post, $this->container_id );
- }
-
- function can_be_trashed(){
- return defined('EMPTY_TRASH_DAYS') && EMPTY_TRASH_DAYS;
- }
- }
- class blcPostMetaManager extends blcContainerManager {
- var $container_class_name = 'blcPostMeta';
- var $meta_type = 'post';
-
- function init(){
- parent::init();
-
- //Intercept 2.9+ style metadata modification actions
- add_action( "added_{$this->meta_type}_meta", array(&$this, 'meta_modified'), 10, 4 );
- add_action( "updated_{$this->meta_type}_meta", array(&$this, 'meta_modified'), 10, 4 );
- add_action( "deleted_{$this->meta_type}_meta", array(&$this, 'meta_modified'), 10, 4 );
- //Also intercept the equivalent actions used in /wp-admin/includes/post.php.
- //(WP is bloody inconsistent. The action names differ by a single character
- //but have different argument counts)
- add_action( "added_{$this->meta_type}meta", array(&$this, 'meta_modified'), 10, 4 );
- add_action( "deleted_{$this->meta_type}meta", array(&$this, 'meta_modified'), 10, 1 );//NB : 1 argument!
-
- //When a post is deleted, also delete the custom field container associated with it.
- add_action('delete_post', array(&$this,'post_deleted'));
- add_action('trash_post', array(&$this,'post_deleted'));
-
- //Re-parse custom fields when a post is restored from trash
- add_action('untrashed_post', array(&$this,'post_untrashed'));
- }
-
- /**
- * Get a list of parseable fields.
- *
- * @return array
- */
- function get_parseable_fields(){
- //Fields = custom field names as entered by the user.
- $fields = array();
-
- if ( is_array($this->plugin_conf->options['custom_fields']) ){
- foreach($this->plugin_conf->options['custom_fields'] as $meta_name){
- $fields[$meta_name] = 'metadata';
- }
- }
-
- return $fields;
- }
-
- /**
- * Instantiate multiple containers of the container type managed by this class.
- *
- * @param array $containers Array of assoc. arrays containing container data.
- * @param string $purpose An optional code indicating how the retrieved containers will be used.
- * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
- *
- * @return array of blcPostMeta indexed by "container_type|container_id"
- */
- function get_containers($containers, $purpose = '', $load_wrapped_objects = false){
- $containers = $this->make_containers($containers);
-
- /*
- When links from custom fields are displayed in Tools -> Broken Links,
- each one also shows the title of the post that the custom field(s)
- belong to. Thus it makes sense to pre-cache the posts beforehand - it's
- faster to load them all at once than to make a separate query for each
- one later.
-
- So make a list of involved post IDs and load them.
-
- Calling get_posts() will automatically populate the post cache, so we
- don't need to actually store the results anywhere in the container object().
- */
- $preload = $load_wrapped_objects || in_array($purpose, array(BLC_FOR_DISPLAY));
- if ( $preload ){
- $post_ids = array();
- foreach($containers as $container){
- $post_ids[] = $container->container_id;
- }
-
- $args = array('include' => implode(',', $post_ids));
- get_posts($args);
- }
-
- return $containers;
- }
-
- /**
- * Create or update synchronization records for all containers managed by this class.
- *
- * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
- * @return void
- */
- function resynch($forced = false){
- global $wpdb; /** @var wpdb $wpdb */
- global $blclog;
-
- if ( $forced ){
- //Create new synchronization records for all posts.
- $blclog->log('...... Creating synch records for all custom fields');
- $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
- SELECT id, '{$this->container_type}', 0
- FROM {$wpdb->posts}
- WHERE
- {$wpdb->posts}.post_status = 'publish'
- AND {$wpdb->posts}.post_type IN ('post', 'page')";
- $wpdb->query( $q );
- $blclog->log(sprintf('...... %d rows affected', $wpdb->rows_affected));
- } else {
- //Delete synch records corresponding to posts that no longer exist.
- $blclog->log('...... Deleting custom field synch records corresponding to deleted posts');
- $q = "DELETE synch.*
- FROM
- {$wpdb->prefix}blc_synch AS synch LEFT JOIN {$wpdb->posts} AS posts
- ON posts.ID = synch.container_id
- WHERE
- synch.container_type = '{$this->container_type}' AND posts.ID IS NULL";
- $wpdb->query( $q );
- $blclog->log(sprintf('...... %d rows affected', $wpdb->rows_affected));
-
- //Remove the 'synched' flag from all posts that have been updated
- //since the last time they were parsed/synchronized.
- $blclog->log('...... Marking custom fields on changed post as unsynched');
- $q = "UPDATE
- {$wpdb->prefix}blc_synch AS synch
- JOIN {$wpdb->posts} AS posts ON (synch.container_id = posts.ID and synch.container_type='{$this->container_type}')
- SET
- synched = 0
- WHERE
- synch.last_synch < posts.post_modified";
- $wpdb->query( $q );
- $blclog->log(sprintf('...... %d rows affected', $wpdb->rows_affected));
-
- //Create synch. records for posts that don't have them.
- $blclog->log('...... Creating custom field synch records for new posts');
- $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
- SELECT id, '{$this->container_type}', 0
- FROM
- {$wpdb->posts} AS posts LEFT JOIN {$wpdb->prefix}blc_synch AS synch
- ON (synch.container_id = posts.ID and synch.container_type='{$this->container_type}')
- WHERE
- posts.post_status = 'publish'
- AND posts.post_type IN ('post', 'page')
- AND synch.container_id IS NULL";
- $wpdb->query($q);
- $blclog->log(sprintf('...... %d rows affected', $wpdb->rows_affected));
- }
- }
-
- /**
- * Mark custom fields as unsynched when they're modified or deleted.
- *
- * @param array|int $meta_id
- * @param int $object_id
- * @param string $meta_key
- * @param string $meta_value
- * @return void
- */
- function meta_modified($meta_id, $object_id = 0, $meta_key= '', $meta_value = ''){
- global $wpdb; /** @var wpdb $wpdb */
-
- //If object_id isn't specified then the hook was probably called from the
- //stupidly inconsistent delete_meta() function in /wp-admin/includes/post.php.
- if ( empty($object_id) ){
- //We must manually retrieve object_id and meta_key from the DB.
- if ( is_array($meta_id) ){
- $meta_id = array_shift($meta_id);
- }
-
- $meta = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->postmeta WHERE meta_id = %d", $meta_id), ARRAY_A );
- if ( empty($meta) ){
- return;
- }
-
- $object_id = $meta['post_id'];
- $meta_key = $meta['meta_key'];
- }
-
-
- //Metadata changes only matter to us if the modified key
- //is one that the user wants checked.
- $conf = blc_get_configuration();
- if ( !is_array($conf->options['custom_fields']) ){
- return;
- }
- if ( !in_array($meta_key, $conf->options['custom_fields']) ){
- return;
- }
-
- $container = blcContainerHelper::get_container( array($this->container_type, intval($object_id)) );
- $container->mark_as_unsynched();
- }
-
- /**
- * Delete custom field synch. records when the post that they belong to is deleted.
- *
- * @param int $post_id
- * @return void
- */
- function post_deleted($post_id){
- //Get the associated container object
- $container = blcContainerHelper::get_container( array($this->container_type, intval($post_id)) );
- //Delete it
- $container->delete();
- //Clean up any dangling links
- blc_cleanup_links();
- }
-
- /**
- * When a post is restored, mark all of its custom fields as unparsed.
- * Called via the 'untrashed_post' action.
- *
- * @param int $post_id
- * @return void
- */
- function post_untrashed($post_id){
- //Get the associated container object
- $container = blcContainerHelper::get_container( array($this->container_type, intval($post_id)) );
- $container->mark_as_unsynched();
- }
-
- /**
- * Get the message to display after $n posts have been deleted.
- *
- * @uses blcAnyPostContainerManager::ui_bulk_delete_message()
- *
- * @param int $n Number of deleted posts.
- * @return string A delete confirmation message, e.g. "5 posts were moved to the trash"
- */
- function ui_bulk_delete_message($n){
- return blcAnyPostContainerManager::ui_bulk_delete_message($n);
- }
-
- /**
- * Get the message to display after $n posts have been trashed.
- *
- * @param int $n Number of deleted posts.
- * @return string A confirmation message, e.g. "5 posts were moved to trash"
- */
- function ui_bulk_trash_message($n){
- return blcAnyPostContainerManager::ui_bulk_trash_message($n);
- }
- }
- ?>