WordPress Logo

WordPress Multisite unfiltered_html Capability

User roles other than Super Admin cannot be assigned the unfiltered_html capability in WordPress Multisite. You can set it, but then WordPress disables the capability after the fact.

This creates a unique WordPress challenge. Your site admins will not have the ability to add a hard coded iframe or embed to a post.

Solution

WordPress does not offer an easy function to find a users role. I have provided a function to achieve that as part of the solution.

/**
 * Simulate assigning the unfiltered_html capability to a role.
 *
 * @return void
 */
add_action('admin_init', 'tsg_kses_remove_filters');
function tsg_kses_remove_filters(): void
{
    if (tsg_user_has_role('editor', wp_get_current_user())) {
        kses_remove_filters();
    }
}

/**
 * Check if a user has a role.
 *
 * @param string $role
 * @param null|WP_User $user
 * @return bool
 */
function tsg_user_has_role(string $role = '', $user = null): bool
{
    if (is_object($user)) {
        $user = $user->ID;
    }

    $user = $user ? new WP_User($user) : wp_get_current_user();

    if (empty($user->roles)) {
        return false;
    }

    if (in_array($role, $user->roles)) {
        return true;
    }

    return false;
}
WordPress Logo

Automatic Featured Image for WordPress Posts

The following script will automatically set the first image in a WordPress post as the Featured Image when the post is saved. You can override the functionality by selecting a Featured Image.

/**
 * @package WordPress
 * @subpackage Automatic Featured Image for WordPress Posts
 * @author That Stevens Guy
 * @phpcs:disable PSR1.Files.SideEffects
 */

/**
 * Transition post status action.
 *
 * @param string $new_status
 * @param string $old_status
 * @param WP_Post $post
 * @return void
 */
add_action('transition_post_status', function (string $new_status, string $old_status, WP_Post $post): void {
    if (defined('REST_REQUEST') && REST_REQUEST) {
        $published_post = $post;

        /**
         * REST requests need to postpone changes until "rest_after_insert_{$post->post_type}".
         *
         * @param WP_Post $post
         * @param WP_REST_Request $request
         * @param bool $creating
         * @return void
         */

        add_action("rest_after_insert_{$post->post_type}", function (
            WP_Post $post,
            WP_REST_Request $request,
            bool $creating
        ) use (
            $new_status,
            $old_status,
            $published_post
        ): void {
            if ($published_post->ID !== $post->ID) {
                return;
            }

            tsg_transition_post_status($new_status, $old_status, $post);
        }, 10, 3);
    } else {
        tsg_transition_post_status($new_status, $old_status, $post);
    }
}, 10, 3);

/**
 * Transition post status function.
 *
 * @param string $new_status
 * @param string $old_status
 * @param WP_Post $post
 * @return void
 */
function tsg_transition_post_status(string $new_status, string $old_status, WP_Post $post): void
{
    // Set the first image in post_content as the Featured Image. If one wasn't set.
    tsg_set_featured_image($post);
}

/**
 * Set the Featured Image automatically.
 *
 * @param WP_Post $post
 * @return void
 */
function tsg_set_featured_image(WP_Post $post): void
{
    if (!in_array($post->post_type, [ 'post' ])) {
        return;
    }

    // Bypass automatic featured image if the post thumbnail was set manually.
    if (has_post_thumbnail($post)) {
        return;
    }

    $attachment_ids = tsg_get_image_attachment_ids_from_post_content(
        $post,
        [
            'get_first_attachment_id' => true,
            'check_aspect_ratio' => true
        ]
    );

    if (!empty($attachment_ids[ 0 ])) {
        update_post_meta($post->ID, '_thumbnail_id', $attachment_ids[ 0 ]);
    }
}

/**
 * Get image attachment ids from post content.
 *
 * @param WP_Post $post
 * @param array $args
 * @return array
 */
function tsg_get_image_attachment_ids_from_post_content(WP_Post $post, array $args = []): array
{
    $args = array_merge([
        'get_first_attachment_id' => false,
        'check_aspect_ratio' => false,
        'horizontal_aspect_ratio' => 2.5,
        'vertical_aspect_ratio' => 2.5
    ], $args);

    $attachment_ids = [];

    $images = tsg_get_images_from_post_content($post);

    if (empty($images)) {
        return $attachment_ids;
    }

    $site_url = parse_url(site_url());

    foreach ($images as $image) {
        // If the image is NOT from the current site, skip it.
        if (strpos($image[ 'src' ], $site_url[ 'host' ] . '/' . explode('/', $image[ 'src' ])[ 3 ]) === false) {
            continue;
        }

        $guid = tsg_get_original_image_src($image[ 'src' ]);

        if (empty($guid)) {
            continue;
        }

        $attachment_id = tsg_get_post_id_by_guid($guid);

        if (empty($attachment_id)) {
            continue;
        }

        if ($args[ 'check_aspect_ratio' ]) {
            $attachment_metadata = get_metadata('post', $attachment_id, '_wp_attachment_metadata', true);

            if (
                !tsg_check_image_aspect_ratio(
                    $attachment_metadata,
                    $args[ 'horizontal_aspect_ratio' ],
                    $args[ 'vertical_aspect_ratio' ]
                )
            ) {
                continue;
            }
        }

        $attachment_ids[] = $attachment_id;

        if ($args[ 'get_first_attachment_id' ]) {
            break;
        }
    }

    return $attachment_ids;
}

/**
 * Get the original image source, size 'full'.
 *
 * @param string $url
 * @param array $args
 * @return string
 */
function tsg_get_original_image_src(string $url, array $args = []): string
{
    if (!$url) {
        return $url;
    }

    $args = array_merge([
        'check_exists' => false,
        'check_filesize' => false,
        'filesize_limit' => 4000000,
        'strip_edit' => false
    ], $args);

    // Strip the thumbnail size at the end of the URL so that we end up with what
    // potentially could be the full size original image source.
    //
    // There is an edge case where the original URL has dimensions in the filename
    // with the same format. These will be skipped, this is unaviodable, particularly
    // for an offsite URL.
    $url = preg_replace("/\-\d{2,4}[xX]\d{2,4}(\.[a-zA-Z]{2,4})$/", '$1', $url);

    // Strip the edit timestamp for WordPress edited images.
    // Turns out this isn't the best idea, end up with unedited images. But can be used for some things.
    if (!empty($args[ 'strip_edit' ])) {
        if (strpos($url, '-e') !== false) {
            $pathinfo = pathinfo($url);

            if (
                !empty($pathinfo[ 'dirname' ]) &&
                !empty($pathinfo[ 'filename' ]) &&
                !empty($pathinfo[ 'extension' ])
            ) {
                $filename_split = array_reverse(explode('-e', $pathinfo[ 'filename' ]));

                if (!empty($filename_split[ 0 ]) && is_int((int)$filename_split[ 0 ])) {
                    unset($filename_split[ 0 ]);
                }

                $url = $pathinfo[ 'dirname' ] . '/' .
                    implode('-e', array_reverse($filename_split)) . '.' . $pathinfo [ 'extension' ];
            }
        }
    }

    // Because we've chopped the URL up so much, we may want to check if the image even exists.
    if (!empty($args[ 'check_exists' ]) || !empty($args[ 'check_filesize' ])) {
        $stream_options = [
            'http' => [
                'user_agent' =>
                    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6"
            ],
            // 'ssl'  => [
            //     'verify_peer' => false,
            //     'verify_peer_name' => false
            // ]
        ];
        $stream_context = stream_context_create($stream_options);
        $headers = @get_headers($url, true, $stream_context);

        if (!empty($args[ 'check_exists' ])) {
            if (empty($headers[ 0 ]) || strpos($headers[ 0 ], '404') !== false) {
                $url = '';
            }
        }

        if ($url && !empty($args[ 'check_filesize' ]) && !empty($args[ 'filesize_limit' ])) {
            if (
                empty($headers[ 'Content-Length' ]) ||
                (int)$headers[ 'Content-Length' ] > (int)$args[ 'filesize_limit' ]
            ) {
                $url = '';
            }
        }
    }

    return $url;
}

/**
 * Get all images from the post content.
 *
 * @param WP_Post $post
 * @return array
 */
function tsg_get_images_from_post_content(WP_Post $post): array
{
    $images = [];

    if (empty($post->post_content)) {
        return $images;
    }

    $content = apply_filters('the_content', $post->post_content);

    preg_match_all('/<img\b[^>]+src=[\'"]([^\'"]+\.(?:jpg|png|jpeg))[\'"][^>]*>/i', $content, $matchesImages);

    if (!empty($matchesImages[ 0 ])) {
        foreach ($matchesImages[ 0 ] as $key => $img) {
            $images[ $key ][ 'img' ] = $img;
            $images[ $key ][ 'src' ] = $matchesImages[ 1 ][ $key ];

            preg_match_all(
                '/(<img\b|(?!^)\G)[^>]*?\b(alt|width|height|srcset|sizes)=([\'"]?)([^>]*?)\3/i',
                $img,
                $matchesAttr
            );

            if (!empty($matchesAttr[ 2 ])) {
                foreach ($matchesAttr[ 2 ] as $attr_key => $attr) {
                    if (!empty($matchesAttr[ 4 ][ $attr_key ])) {
                        $images[ $key ][ $attr ] = $matchesAttr[ 4 ][ $attr_key ];
                    }
                }
            }
        }
    }

    $images = apply_filters('tsg_get_images_from_post_content', $images, $post);

    return $images;
}

/**
 * Get check if an image fits within a suitable aspect ratio.
 *
 * @param array $image [ 'height' => int, 'width' => int ]
 * @param float $horizontal_aspect_ratio
 * @param float $vertical_aspect_ratio
 * @return bool
 */
function tsg_check_image_aspect_ratio(
    array $image,
    float $horizontal_aspect_ratio = 2.5,
    float $vertical_aspect_ratio = 2.5
): bool {
    if (empty($image[ 'width' ]) || empty($image[ 'height' ])) {
        return false;
    }

    $calculated_horizontal_aspect_ratio = (int)$image[ 'width' ] / (int)$image[ 'height' ];
    $calculated_vertical_aspect_ratio = (int)$image[ 'height' ] / (int)$image[ 'width' ];

    if (
        $calculated_horizontal_aspect_ratio > $horizontal_aspect_ratio ||
        $calculated_vertical_aspect_ratio > $vertical_aspect_ratio
    ) {
        return false;
    }

    return true;
}

/**
 * Get post ID by guid.
 *
 * @param string $guid
 * @return int ID if found, 0 if not
 */
function tsg_get_post_id_by_guid(string $guid): int
{
    global $wpdb;

    $post_id = $wpdb->get_var(
        $wpdb->prepare("
            SELECT ID
            FROM $wpdb->posts
            WHERE instr( guid, '%s' ) > 0
        ", $guid)
    );

    return intval($post_id);
}

https://gist.github.com/ThatStevensGuy/7020010fe667106f79d2556f386933d0

Telstra Gateway Max is a Piece of Junk

The Gateway Max is a Piece of Junk

Telstra NBN customers are provided with a Telstra Gateway Max to connect their NBN service. These devices come in 3 tiers and the base unit is a re-branded Sagem f@st 5355 with firmware restrictions. I’m not going to moan about how bad it is. I’d rather just fix it properly.

It is possible to use another NBN ready router. But there is a compromise if you want to maintain Telstra VOIP. Customers are provided with VOIP to replace the disconnected landline. But Telstra do not provide SIP details to residential accounts.

Configuration

You should connect the Telstra Gateway Max to the internet first. This will authenticate the connection. Then you can connect your desired router. No login details are required to authenticate the connection. I’ve personally configured this setup on a FTTN (VDSL) connection using a Fritz!Box 7490.

Telstra NBN - Router - Telstra Gateway - Phone

Plug a network cable into the WAN port of the Telstra Gateway. When you can see that the Telstra Gateway has internet, you’ll need to port forward in your preferred router ports 3478, 5004, 5060 and 5061 for both TCP & UDP. I find it easier to jump between the routers using a separate network cable. When the phone line has a dial tone, go back into the Telstra router and turn off WiFi, DHCP and the Media Server to save power.

Note: If you run into connection issues, Telstra will require that you have their Gateway connected directly to the internet for tech support to help you.