get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}blc_instances WHERE instance_id=%d LIMIT 1", $arg ), ARRAY_A ); if ( is_array( $arr ) ) { //Loaded successfully $this->set_values( $arr ); } else { //Link instance not found. The object is invalid. } } elseif ( is_array( $arg ) ) { $this->set_values( $arg ); //Is this a new instance? $this->is_new = empty( $this->instance_id ); } else { $this->is_new = true; } } /** * blcLinkInstance::blcLinkInstance() * Old-style constructor for PHP 4. Do not use. * * @param mixed $arg * @return void */ function blcLinkInstance( $arg = null ) { $this->__construct( $arg ); } /** * blcLinkInstance::set_values() * Set property values to the ones provided in an array (doesn't sanitize). * * @param array $arr An associative array * @return void */ function set_values( $arr ) { foreach ( $arr as $key => $value ) { $this->$key = $value; } } /** * Replace this instance's URL with a new one. * Warning : this shouldn't be called directly. Use blcLink->edit() instead. * * @param string $new_url * @param string $old_url * @param string $new_text * @return bool|WP_Error True on success, or an instance of WP_Error if something went wrong. */ function edit( $new_url, $old_url = '', $new_text = null ) { //Get the container that contains this link $container = $this->get_container(); if ( is_null( $container ) ) { return new WP_Error( 'container_not_found', sprintf( __( 'Container %1$s[%2$d] not found', 'broken-link-checker' ), $this->container_type, $this->container_id ) ); } //Get the parser. $parser = $this->get_parser(); if ( is_null( $parser ) ) { return new WP_Error( 'parser_not_found', sprintf( __( "Parser '%s' not found.", 'broken-link-checker' ), $this->parser_type ) ); } //If the old URL isn't specified get it from the link record if ( empty( $old_url ) ) { $old_url = $this->get_url(); } //Attempt to modify the link(s) $result = $container->edit_link( $this->container_field, $parser, $new_url, $old_url, $this->raw_url, $new_text ); if ( is_string( $result ) ) { //If the modification was successful, the container will return //the new raw_url for the instance. Save the URL and return true, //indicating success. $this->raw_url = $result; return true; } elseif ( is_array( $result ) ) { //More advanced containers/parsers may return an array of values to //modify several fields at once. $allowed_fields = array( 'raw_url', 'link_text', 'link_context' ); foreach ( $result as $key => $value ) { if ( in_array( $key, $allowed_fields ) ) { $this->$key = $value; } } return true; } else { //Otherwise, it will return an error object. In this case we'll //just pass it back to the caller and let them sort it out. return $result; } } /** * blcLinkInstance::unlink() * Remove this instance from the post/blogroll/etc. Also deletes the appropriate DB record(s). * * @return bool|WP_Error */ function unlink( $url = null ) { //Get the container that contains this link $container = $this->get_container(); if ( is_null( $container ) ) { return new WP_Error( 'container_not_found', sprintf( __( 'Container %1$s[%2$d] not found', 'broken-link-checker' ), $this->container_type, $this->container_id ) ); } //Get the parser. $parser = $this->get_parser(); if ( is_null( $parser ) ) { return new WP_Error( 'parser_not_found', sprintf( __( "Parser '%s' not found.", 'broken-link-checker' ), $this->parser_type ) ); } //If the old URL isn't specified get it from the link record if ( empty( $url ) ) { $url = $this->get_url(); } //Attempt to remove the link(s) return $container->unlink( $this->container_field, $parser, $url, $this->raw_url ); } /** * Remove the link instance record from database. Doesn't affect the thing that contains the link. * * @return mixed 1 on success, 0 if the instance wasn't found, false on error */ function forget() { global $wpdb; /** @var wpdb $wpdb */ if ( ! empty( $this->instance_id ) ) { $rez = $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}blc_instances WHERE instance_id=%d", $this->instance_id ) ); return $rez; } else { return false; } } /** * Store the link instance in the database. * Saving the instance will also implicitly save the link record associated with it, if it wasn't already saved. * * @return bool TRUE on success, FALSE on error */ function save() { global $wpdb; /** @var wpdb $wpdb */ //Refresh the locally cached link & container properties, in case //the objects have changed since they were set. if ( ! is_null( $this->_link ) ) { //If we have a link object assigned, but it's new, it won't have a DB ID yet. //We need to save the link to get the ID and be able to maintain the link <-> instance //association. if ( $this->_link->is_new ) { $rez = $this->_link->save(); if ( ! $rez ) { return false; } } $this->link_id = $this->_link->link_id; } if ( ! is_null( $this->_container ) ) { $this->container_type = $this->_container->container_type; $this->container_id = $this->_container->container_id; } //If the link is new, insert a new row into the DB. Otherwise update the existing row. if ( $this->is_new ) { $rez = $wpdb->query( $wpdb->prepare( "INSERT INTO {$wpdb->prefix}blc_instances ( link_id, container_type, container_id, container_field, parser_type, link_text, link_context, raw_url ) VALUES( %d, %s, %d, %s, %s, %s, %s, %s )", $this->link_id, $this->container_type, $this->container_id, $this->container_field, $this->parser_type, $this->link_text, $this->link_context, $this->raw_url ) ); $rez = false !== $rez; if ( $rez ) { $this->instance_id = $wpdb->insert_id; //If the instance was successfully saved then it's no longer "new". $this->is_new = ! $rez; } return $rez; } else { $rez = false !== $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}blc_instances SET link_id = %d, container_type = %s, container_id = %d, container_field = %s, parser_type = %s, link_text = %s, link_context = %s, raw_url = %s WHERE instance_id = %d", $this->link_id, $this->container_type, $this->container_id, $this->container_field, $this->parser_type, $this->link_text, $this->link_context, $this->raw_url, $this->instance_id ) ); if ( $rez ) { //FB::info($this, "Instance updated"); } else { //FB::error("DB error while updating instance {$this->instance_id} : {$wpdb->last_error}"); } return $rez; } } /** * Get the URL associated with this instance. * * @return string The associated URL, or an empty string if the instance is currently not assigned to any link. */ function get_url() { $link = $this->get_link(); if ( ! is_null( $link ) ) { return $link->url; } else { return ''; } } /** * Get the container object associated with this link instance * * @return blcContainer|null */ function get_container() { if ( is_null( $this->_container ) ) { $this->_container = blcContainerHelper::get_container( array( $this->container_type, $this->container_id ) ); } return $this->_container; } /** * Set a new container for the link instance. * * @param blcContainer $new_container * @param string $field * @return void */ function set_container( &$new_container, $field = '' ) { $this->_container = &$new_container; $this->container_field = $field; if ( ! is_null( $new_container ) ) { $this->container_type = $new_container->container_type; $this->container_id = $new_container->container_id; } else { $this->container_type = ''; $this->container_id = 0; } } /** * Get the parser associated with this link instance. * * @return blcParser|null */ function get_parser() { if ( is_null( $this->_parser ) ) { $this->_parser = blcParserHelper::get_parser( $this->parser_type ); } return $this->_parser; } /** * Set a new parser fo this link instance. * * @param blcParser|null $new_parser * @return void */ function set_parser( &$new_parser ) { $this->_parser = &$new_parser; if ( is_null( $new_parser ) ) { $this->parser_type = ''; } else { $this->parser_type = $new_parser->parser_type; } } /** * Get the link object associated with this link intance. * * @return blcLink|null */ function get_link() { if ( ! is_null( $this->_link ) ) { return $this->_link; } if ( empty( $this->link_id ) ) { return null; } $this->_link = new blcLink( $this->link_id ); return $this->_link; } /** * Set the link associated with this link instance. * * @param blcLink $new_link * @return void */ function set_link( $new_link ) { $this->_link = $new_link; if ( is_null( $new_link ) ) { $this->link_id = 0; } else { $this->link_id = $new_link->link_id; } } /** * Get the link text for printing in the "Broken Links" table. * * @param string $context How to filter the link text. Optional, defaults to 'display'. * @return string HTML */ function ui_get_link_text( $context = 'display' ) { $parser = $this->get_parser(); if ( ! is_null( $parser ) ) { $text = $parser->ui_get_link_text( $this, $context ); } else { $text = strip_tags( $this->link_text ); } if ( empty( $text ) ) { $text = '(None)'; } return $text; } /** * Get action links that should be displayed in the "Source" column of the "Broken Links" table. * * @return array An array of HTML links. */ function ui_get_action_links() { //The container is responsible for generating the links. $container = $this->get_container(); if ( ! is_null( $container ) ) { return $container->ui_get_action_links( $this->container_field ); } else { //No valid container = no links. return array(); } } /** * Get the HTML describing the "source" of the instance. For example, for links found in posts, * this could be the post title. * * @param string $context How to filter the output. Optional, defaults to 'display'. * @return string HTML */ function ui_get_source( $context = 'display' ) { //The container is also responsible for generating the "Source" column HTML. $container = $this->get_container(); if ( ! is_null( $container ) ) { return $container->ui_get_source( $this->container_field, $context ); } else { //No valid container = generate some bare-bones debug output. return sprintf( '%s[%d] : %s', $this->container_type, $this->container_id, $this->container_field ); } } /** * Check if the link text associated with this instance can be edited. * * @return bool */ public function is_link_text_editable() { $parser = $this->get_parser(); if ( null === $parser ) { return false; } return $parser->is_link_text_editable(); } /** * Check if the URL of this instance can be edited. * * @return bool */ public function is_url_editable() { $parser = $this->get_parser(); if ( null === $parser ) { return false; } return $parser->is_url_editable(); } } /** * Get all link instances associated with one or more links. * * @param array $link_ids Array of link IDs. * @param string $purpose An optional code indicating how the instances will be used. Available predefined constants : BLC_FOR_DISPLAY, BLC_FOR_EDITING * @param bool $load_containers Preload containers regardless of purpose. Defaults to false. * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose. Defaults to false. * @param bool $include_invalid Include instances that refer to not-loaded containers or parsers. Defaults to false. * @return blcLinkInstance[] An array indexed by link ID. Each item of the array will be an array of blcLinkInstance objects. */ function blc_get_instances( $link_ids, $purpose = '', $load_containers = false, $load_wrapped_objects = false, $include_invalid = false ) { global $wpdb; /** @var wpdb $wpdb */ if ( empty( $link_ids ) ) { return array(); } $link_ids_in = implode( ', ', $link_ids ); $q = "SELECT * FROM {$wpdb->prefix}blc_instances WHERE link_id IN ($link_ids_in)"; //Skip instances that reference containers or parsers that aren't currently loaded if ( ! $include_invalid ) { $manager = blcModuleManager::getInstance(); $active_containers = $manager->get_escaped_ids( 'container' ); $active_parsers = $manager->get_escaped_ids( 'parser' ); $q .= " AND container_type IN ({$active_containers}) "; $q .= " AND parser_type IN ({$active_parsers}) "; } $results = $wpdb->get_results( $q, ARRAY_A ); //phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared if ( empty( $results ) ) { return array(); } //Also retrieve the containers, if it could be useful. $load_containers = $load_containers || in_array( $purpose, array( BLC_FOR_DISPLAY, BLC_FOR_EDITING ) ); if ( $load_containers ) { //Collect a list of (container_type, container_id) pairs $container_ids = array(); foreach ( $results as $result ) { array_push( $container_ids, array( $result['container_type'], intval( $result['container_id'] ) ) ); } $containers = blcContainerHelper::get_containers( $container_ids, $purpose, '', $load_wrapped_objects ); } //Create an object for each instance and group them by link ID $instances = array(); foreach ( $results as $result ) { $instance = new blcLinkInstance( $result ); //Assign a container to the link instance, if available if ( $load_containers && ! empty( $containers ) ) { $key = $instance->container_type . '|' . $instance->container_id; if ( isset( $containers[ $key ] ) ) { $instance->_container = $containers[ $key ]; } } if ( isset( $instances[ $instance->link_id ] ) ) { array_push( $instances[ $instance->link_id ], $instance ); } else { $instances[ $instance->link_id ] = array( $instance ); } } return $instances; } /** * Get the number of instances that reference only currently loaded containers and parsers. * * @return int */ function blc_get_usable_instance_count() { global $wpdb; /** @var wpdb $wpdb */ $q = "SELECT COUNT(instance_id) FROM {$wpdb->prefix}blc_instances WHERE 1"; //Skip instances that reference containers or parsers that aren't currently loaded $manager = blcModuleManager::getInstance(); $active_containers = $manager->get_escaped_ids( 'container' ); $active_parsers = $manager->get_escaped_ids( 'parser' ); $q .= " AND container_type IN ({$active_containers}) "; $q .= " AND parser_type IN ({$active_parsers}) "; return $wpdb->get_var( $q ); //phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } /** * Remove instances that reference invalid containers or containers/parsers that are not currently loaded * * @return bool */ function blc_cleanup_instances() { global $wpdb; /** @var wpdb $wpdb */ global $blclog; //Delete all instances that reference non-existent containers $start = microtime( true ); $q = "DELETE instances.* FROM {$wpdb->prefix}blc_instances AS instances LEFT JOIN {$wpdb->prefix}blc_synch AS synch ON instances.container_type = synch.container_type AND instances.container_id = synch.container_id WHERE synch.container_id IS NULL"; $rez = $wpdb->query( $q ); //phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $elapsed = microtime( true ) - $start; $blclog->log( sprintf( '... %d instances deleted in %.3f seconds', $wpdb->rows_affected, $elapsed ) ); //Delete instances that reference containers and parsers that are no longer active $start = microtime( true ); $manager = blcModuleManager::getInstance(); $active_containers = $manager->get_escaped_ids( 'container' ); $active_parsers = $manager->get_escaped_ids( 'parser' ); $q = "DELETE instances.* FROM {$wpdb->prefix}blc_instances AS instances WHERE instances.container_type NOT IN ({$active_containers}) OR instances.parser_type NOT IN ({$active_parsers})"; $rez2 = $wpdb->query( $q ); //phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $elapsed = microtime( true ) - $start; $blclog->log( sprintf( '... %d more instances deleted in %.3f seconds', $wpdb->rows_affected, $elapsed ) ); return ( false !== $rez ) && ( false !== $rez2 ); } }//class_exists