Q:

Codeigniter blog application bug: form is loaded without validation errors

I am working on a blog application in Codeigniter 3.1.8 and Bootstrap 4. I have an "Edit post" form with validation.
If validation fails (because the Title field has been emptied, for example), the form should reload with validation errors.
My update() (lives in the Posts controller) method is wrong: it uses a redirect, so the form is reloaded without validation errors, to its initial state.
public function edit($id) {
    // Only logged in users can edit posts
    if (!$this->session->userdata('is_logged_in')) {
        redirect('login');
    }

    $data = $this->Static_model->get_static_data();
    $data['pages'] = $this->Pages_model->get_pages();
    $data['categories'] = $this->Categories_model->get_categories();
    $data['posts'] = $this->Posts_model->sidebar_posts($limit=5, $offset=0);
    $data['post'] = $this->Posts_model->get_post($id);
    if ($this->session->userdata('user_id') == $data['post']->author_id) {
        $data['tagline'] = 'Edit the post "' . $data['post']->title . '"';
        $this->load->view('partials/header', $data);
        $this->load->view('edit-post');
        $this->load->view('partials/footer');
    } else {
        /* If the current user is not the author
        of the post do not alow edit */
        redirect('/' . $id);
    }
}

public function update() {
    // Form data validation rules
    $this->form_validation->set_rules('title', 'Title', 'required',  array('required' => 'The %s field can not be empty'));
    $this->form_validation->set_rules('desc', 'Short description', 'required',  array('required' => 'The %s field can not be empty'));
    $this->form_validation->set_rules('body', 'Body', 'required',  array('required' => 'The %s field can not be empty'));
    $this->form_validation->set_error_delimiters('<p class="error-message">', '</p>');

    $id = $this->input->post('id');

    // Update slug (from title)
    if (!empty($this->input->post('title'))) {
        $slug = url_title($this->input->post('title'), 'dash', TRUE);
        $slugcount = $this->Posts_model->slug_count($slug);
        if ($slugcount > 0) {
            $slug = $slug."-".$slugcount;
        }
    } else {
        $slug = $this->input->post('slug');
    }

// Upload image
    $config['upload_path'] = './assets/img/posts';
    $config['allowed_types'] = 'jpg|png';
    $config['max_size'] = '2048';

    $this->load->library('upload', $config);

    if(!$this->upload->do_upload()){
        $errors = array('error' => $this->upload->display_errors());
        $post_image = $this->input->post('postimage');
    } else {
        $data = array('upload_data' => $this->upload->data());
        $post_image = $_FILES['userfile']['name'];
    }

    if ($this->form_validation->run()) {
        $this->Posts_model->update_post($id, $post_image, $slug);
        $this->session->set_flashdata('post_updated', 'Your post has been updated');
        redirect('/' . $slug);
    } else {
        redirect('/posts/edit/' . $slug);
    }
}

I am almost certain the problem is this line: redirect('/posts/edit/' . $slug); but I have not been able to find a viable alternative.
Using $this->edit($id) instead of redirect('/posts/edit/' . $slug); does not work either. I wish it would, because I want to keep the code DRY.
What shall I change?

Edit. I did this:
if ($this->form_validation->run()) {
        $this->Posts_model->update_post($id, $post_image, $slug);
        $this->session->set_flashdata('post_updated', 'Your post has been updated');
        redirect('/' . $slug);
} else {
        $this->form_validation->run();
        $this->session->set_flashdata('errors', validation_errors());
        var_dump($this->session->flashdata('errors'));
        //redirect('/posts/edit/' . $slug);
}

The var_dump($this->session->flashdata('errors')); returns all the validation errors.
I wish to add the class has-error to the form-group and append <p class="error-message">The Title field is required.</p>.
<div class="form-group has-error">
  <input type="text" name="title" id="title" class="form-control error" placeholder="Title" data-rule-required="true" value="Learn to code with us" aria-invalid="true">
  <p class="error-message">The Title field is required.</p>
</div>

A:

You have 3 options:

Use flash data in update method on failure (you are already using it on success). Simply assign the errors to a flash data variable and get it after you redirect back to edit.
Combine edit and update methods (most common for non-ajax usage).
Use ajax and return json encoded strings for errors or success messages.

Option 2:
This option also solves the potential authentication issue pointed out in the comments.
Please read the comments embedded in the code.
public function edit($id) {
        // Only logged in users can edit posts
        if (!$this->session->userdata('is_logged_in')) {
            redirect('login');
        }

        $data['post'] = $this->Posts_model->get_post($id);

        if ($this->session->userdata('user_id') == $data['post']->author_id) {
            show_error('Access denied'); // function exits
        }

        if ($_POST) {

            $this->form_validation->set_rules('title', 'Title', 'required', array('required' => 'The %s field can not be empty'));
            $this->form_validation->set_rules('desc', 'Short description', 'required', array('required' => 'The %s field can not be empty'));
            $this->form_validation->set_rules('body', 'Body', 'required', array('required' => 'The %s field can not be empty'));
            $this->form_validation->set_error_delimiters('<p class="error-message">', '</p>');

            //$id = $this->input->post('id'); not required anymore

            $config['upload_path'] = './assets/img/posts';
            $config['allowed_types'] = 'jpg|png';
            $config['max_size'] = '2048';

            $this->load->library('upload', $config);

            if ($this->form_validation->run() && $this->upload->do_upload()) {
                // always use the name from the upload lib
                // sometimes it changes it in case of duplicates (read docs for more)
                $post_image = $this->upload->data('file_name');

                // doesn't make sense with title validation rule, this will always be true to get
                // passed validation
                if (!empty($this->input->post('title'))) {
                    $slug = url_title($this->input->post('title'), 'dash', TRUE);
                    $slugcount = $this->Posts_model->slug_count($slug);
                    if ($slugcount > 0) {
                        $slug = $slug . "-" . $slugcount;
                    }
                } else {
                    $slug = $this->input->post('slug');
                }

                $this->Posts_model->update_post($id, $post_image, $slug);
                $this->session->set_flashdata('post_updated', 'Your post has been updated');
                redirect('/' . $slug);
            } else {
                $data['errors'] = validation_errors() . $this->upload->display_errors();
            }
        }

        $data = $this->Static_model->get_static_data();
        $data['pages'] = $this->Pages_model->get_pages();
        $data['categories'] = $this->Categories_model->get_categories();
        $data['posts'] = $this->Posts_model->sidebar_posts($limit = 5, $offset = 0);
        $data['tagline'] = 'Edit the post "' . $data['post']->title . '"';
        $this->load->view('partials/header', $data);
        $this->load->view('edit-post');
        $this->load->view('partials/footer');
    }

A:

I have managed to get the desired result using set_flashdata() as suggested by @Alex
In the controller I have:
public function update() {
    // Form data validation rules
    $this->form_validation->set_rules('title', 'Title', 'required',  array('required' => 'The %s field can not be empty'));
    $this->form_validation->set_rules('desc', 'Short description', 'required',  array('required' => 'The %s field can not be empty'));
    $this->form_validation->set_rules('body', 'Body', 'required',  array('required' => 'The %s field can not be empty'));
    $this->form_validation->set_error_delimiters('<p class="error-message">', '</p>');

    $id = $this->input->post('id');

    // Update slug (from title)
    if (!empty($this->input->post('title'))) {
        $slug = url_title($this->input->post('title'), 'dash', TRUE);
        $slugcount = $this->Posts_model->slug_count($slug);
        if ($slugcount > 0) {
            $slug = $slug."-".$slugcount;
        }
    } else {
        $slug = $this->input->post('slug');
    }

// Upload image
    $config['upload_path'] = './assets/img/posts';
    $config['allowed_types'] = 'jpg|png';
    $config['max_size'] = '2048';

    $this->load->library('upload', $config);

    if(!$this->upload->do_upload()){
        $errors = array('error' => $this->upload->display_errors());
        $post_image = $this->input->post('postimage');
    } else {
        $data = array('upload_data' => $this->upload->data());
        $post_image = $_FILES['userfile']['name'];
    }

    if ($this->form_validation->run()) {
        $this->Posts_model->update_post($id, $post_image, $slug);
        $this->session->set_flashdata('post_updated', 'Your post has been updated');
        redirect('/' . $slug);
    } else {
        $this->form_validation->run();
        $this->session->set_flashdata('errors', validation_errors());
        redirect('/posts/edit/' . $slug);
    }
}

In the view:
<?php if ($this->session->flashdata('errors')) {
         $errors = $this->session->flashdata('errors');
         echo '<div class="error-group alert alert-warning alert-dismissible">' . "\n";
         echo '<button type="button" class="close" data-dismiss="alert">&times;</button>' . "\n";
         echo $errors;
         echo '<p class="error-message">We have restored the post.</p>';
         echo '</div>';
} ?>

