Forum

Thread tagged as: Question, Suggestions, Runway

Republish a collection

I've just updated a template, as I've realised I need a slug field. It would be great to be able to Republish my collection, so that it updated. Similarly I've removed some fields, so Republish would remove a load of redundant data for me. Otherwise, I think I'm going to have to save each item my collection again.

I asked before about slug field uniqueness. You said that you weren't going to ensure slug uniqueness (say by adding a "1" to the end of the slug if that slug already exists). To me, that would be welcome, even if it meant adding a unique="true" in the template. I see that slugs can now be editable, which again I think is neat.

Is there a way for me to create my slug out of two fields? I guess I could create a composite field and then slug that? How about including the category in the slug field? I think that would be enough to guarantee uniqueness.

Paul Bell

Paul Bell 0 points

  • 6 years ago
Drew McLellan

Drew McLellan 2638 points
Perch Support

You can specify multiple IDs in the for attribute:

for="title date_year date_month"

I think you misunderstand the nature of republishing. The republish option recompiles the HTML output based on the stored data. This doesn't apply to collections, because they don't precompile the HTML.

Can the slug field be a category field though? Or a relationship field?

Re republishing, it does sound like I thought it did more than it does. I understood that it would recompile the HTML, but I thought it would also create any extra fields (such as slug fields) that might be in the template. Guess it doesn't, and I'll have to go through and save all my entries again. I guess in future I should make sure my templates are nailed with a few example bits of data before I enter them all?

Drew McLellan

Drew McLellan 2638 points
Perch Support

Can the slug field be a category field though? Or a relationship field?

It can't. A relationship is literally a link to another whole item of content. What would the value of that slug be?

I guess in future I should make sure my templates are nailed with a few example bits of data before I enter them all?

A little bit of planning before entering large amounts of content is always a sensible idea.

That's a fair question - I guess the value of the slug would be a slugged version of the "title" of that content, but I can see that as it's relationship, the item could get changed at a later date. Also, I hadn't thought that an item could be related to more than one item or category, so maybe that's not such a good plan.

I'll bear in mind and try to plan better in future - I guess ideally I would be able to work in either order, but I can see the sense in working this way.

I'm still struggling now to see a way to guarantee slug uniqueness in a way that won't involve my client having to think about it. Any thoughts? My "projects" collection is really just a title, and some relationships to churches and some categories. I have three projects with the same name, but related to different churches. They're probably in the same category to that might not help. Any ideas? I don't want to have to manually edit the slug, or even expose it to the client. Also they wouldn't get a warning about the slug not being unique, and I'd only find out about it when something on the site links to the wrong place.

Drew McLellan

Drew McLellan 2638 points
Perch Support

In what context should it be unique? Within the collection?

Yes, although within the site would be fine too - in this particular context it would have the same effect I imagine.

Drew McLellan

Drew McLellan 2638 points
Perch Support

Within the entire site is actually very complex. Within a collection might be possible at some stage.

That would be great, Drew. Appreciate your help. Perch is working brilliantly for us and with a few tweaks it will be perfect.

I've been looking at this today - I've written a new fieldtype based on the Perch native "slug" but it checks for the uniqueness of the slug... it's called brd_slug. Here it is:

<?php
class PerchFieldType_brd_slug extends PerchFieldType
{
    public function add_page_resources()
    {
        $Perch = Perch::fetch();
        $Perch->add_javascript(PERCH_LOGINPATH.'/core/assets/js/slugs.js');
    }

    public function render_inputs($details=array())
    {
        if ($this->Tag->editable()) {

            $s = '';
            $attrs = '';

            $id = $this->Tag->id();
            $value = $this->Form->get($details, $id, $this->Tag->default(), $this->Tag->post_prefix());

            if (!$value) {
                if (isset($details[$id])) {
                    $value = $details[$id];
                }
            }

            if ($value && $this->Tag->indelible()) {
                $attrs .= 'disabled="disabled" ';
            }else{

                if ($this->Tag->for()) {
                    $parts = $this->break_for_string($this->Tag->for());

                    $tmp = array();
                    if (PerchUtil::count($parts)) {
                        foreach($parts as $field) {
                            $tmp[] = str_replace('__', '_', $this->Tag->post_prefix()).$field;
                        }
                    }

                    if (!$value) $attrs .= 'data-slug-for="'.PerchUtil::html(implode(' ',$tmp), true).'"';

                }

            }

            $s = $this->Form->text($this->Tag->input_id(), $value, $this->Tag->size(), $this->Tag->maxlength(), 'text', $attrs);

            return $s;
        }

        return '';
    }

    public function get_raw($post=false, $Item=false)
    {
        $value = false;

        if ($post===false) {
            $post = $_POST;
        }

        $id = $this->Tag->id();
        if (isset($post[$id])) {
            $this->raw_item = trim(PerchUtil::safe_stripslashes($post[$id]));
            $value = $this->raw_item;
        }

        // Indelible?
        if ($this->Tag->indelible()) {
            // if it's indelible, just return the previous value.

            $prev_value = false;

            if (is_object($Item)){
                $json = PerchUtil::json_safe_decode($Item->itemJSON(), true);

                if (PerchUtil::count($json) && isset($json[$this->Tag->id()])) {
                    $prev_value = $json[$this->Tag->id()];
                }
            }elseif (is_array($Item)) {
                $json = $Item;
                if (PerchUtil::count($json) && isset($json[$this->Tag->id()])) {
                    $prev_value = $json[$this->Tag->id()];
                }
            }

            if ($prev_value) return $this->unique_slug($prev_value);
        }

        // Editable + value?
        if ($this->Tag->editable() && $value) {
            // return the user's value
            return $this->unique_slug($value);
        }

        if ($this->Tag->for()) {

            $parts = $this->break_for_string($this->Tag->for());
            if (PerchUtil::count($parts)) {
                $str = array();
                foreach($parts as $part) {
                    if (isset($post[$part])) {
                        $str[] = trim(PerchUtil::safe_stripslashes($post[$part]));
                    }
                }
                return $this->unique_slug(PerchUtil::urlify(implode(' ', $str)));
            }

            if (isset($post[$this->Tag->for()])) {
                return $this->unique_slug(PerchUtil::urlify(trim(PerchUtil::safe_stripslashes($post[$this->Tag->for()]))));
            }
        }

        return '';
    }

    public function get_search_text($raw=false)
    {
        if ($raw===false) $raw = $this->get_raw();

        $parts = explode('-', $raw);
        return implode(' ', $parts);
    }

    private function break_for_string($for)
    {
        return explode(' ', $for);
    }

    private function unique_slug($slug)
    {
        // Get existing slugs
        $collection = $this->Tag->collection();

        if(!isset($collection) || $collection == '')
        {
            return $slug;
        }

        $DB = PerchDB::fetch();     
        $collection_info = $DB->get_row('SELECT * FROM perch2_collections c WHERE c.collectionKey = '. $DB->pdb($collection));

        $collection_id = $collection_info['collectionID'];

        if(!isset($collection_id) || !is_numeric($collection_id))
        {
            return $slug;
        }

        $id = $this->Tag->id();


        // Need to exclude the current item
        $item_id = $_GET['itm'];

        $item_sql = '';
        if(isset($item_id) && is_numeric($item_id))
        {
            $item_sql = " AND ci.itemID <> $item_id";
        }

        // All items from the colection
        $items = $DB->get_rows('SELECT itemJSON FROM perch2_collection_items ci, perch2_collection_revisions r WHERE ci.itemID=r.itemID AND ci.itemRev=r.itemRev AND ci.collectionID = '.$DB->pdb($collection_id). ' AND r.collectionID= '.$DB->pdb($collection_id) . $item_sql);

        // Build an array of slugs
        foreach($items as $item)
        {
            $item_data = PerchUtil::json_safe_decode($item['itemJSON']);
            if(isset($item_data->$id) &&  $item_data->$id !=='')
            {
                $slugs[] = $item_data->$id;
            }
        }

        // Is our proposed slug in the list?  If yes, return it
        if( in_array($slug, $slugs))
        {
            $try = $slug;
            $i = 1; 
            while(in_array($try, $slugs))
            {
                $try = $slug.'-'.$i;
                $i++;
            }

            return $try;
        }
        else
        {
            return $slug;           
        }
    }
}
?>

In the template, you need to specify the collection

<perch:content id="project_slug" type="brd_slug" for="description" collection="Projects" editable="true" label="Project Slug" />

(I could get the collection from the URL, but this way it only triggers the unique slug functionality when I really want it to - otherwise it just works like regular slug).

Any thoughts/comments/suggestions on this? (It's my first time writing code to query the Perch database, so it can probably be improved, but it does seem to be working reliably here to give a unique slug by adding a number to the end if the slug has already been used).

Drew McLellan

Drew McLellan 2638 points
Perch Support

I'd be a bit worried as to how that might scale. It could be better to inspect the index, which is designed for these sorts of things.

Ah, thanks, Drew. I hadn't noticed that the slug is already in the index, so that avoids me having to mess with any JSON etc. I'll rework accordingly.

Here's my latest version using the index. No warranty, but it seems to be working.

<?php
class PerchFieldType_brd_slug extends PerchFieldType
{
    public function add_page_resources()
    {
        $Perch = Perch::fetch();
        $Perch->add_javascript(PERCH_LOGINPATH.'/core/assets/js/slugs.js');
    }

    public function render_inputs($details=array())
    {
        if ($this->Tag->editable()) {

            $s = '';
            $attrs = '';

            $id = $this->Tag->id();
            $value = $this->Form->get($details, $id, $this->Tag->default(), $this->Tag->post_prefix());

            if (!$value) {
                if (isset($details[$id])) {
                    $value = $details[$id];
                }
            }

            if ($value && $this->Tag->indelible()) {
                $attrs .= 'disabled="disabled" ';
            }else{

                if ($this->Tag->for()) {
                    $parts = $this->break_for_string($this->Tag->for());

                    $tmp = array();
                    if (PerchUtil::count($parts)) {
                        foreach($parts as $field) {
                            $tmp[] = str_replace('__', '_', $this->Tag->post_prefix()).$field;
                        }
                    }

                    if (!$value) $attrs .= 'data-slug-for="'.PerchUtil::html(implode(' ',$tmp), true).'"';

                }

            }

            $s = $this->Form->text($this->Tag->input_id(), $value, $this->Tag->size(), $this->Tag->maxlength(), 'text', $attrs);

            return $s;
        }

        return '';
    }

    public function get_raw($post=false, $Item=false)
    {
        $value = false;

        if ($post===false) {
            $post = $_POST;
        }

        $id = $this->Tag->id();
        if (isset($post[$id])) {
            $this->raw_item = trim(PerchUtil::safe_stripslashes($post[$id]));
            $value = $this->raw_item;
        }

        // Indelible?
        if ($this->Tag->indelible()) {
            // if it's indelible, just return the previous value.

            $prev_value = false;

            if (is_object($Item)){
                $json = PerchUtil::json_safe_decode($Item->itemJSON(), true);

                if (PerchUtil::count($json) && isset($json[$this->Tag->id()])) {
                    $prev_value = $json[$this->Tag->id()];
                }
            }elseif (is_array($Item)) {
                $json = $Item;
                if (PerchUtil::count($json) && isset($json[$this->Tag->id()])) {
                    $prev_value = $json[$this->Tag->id()];
                }
            }

            if ($prev_value) return $this->unique_slug($prev_value);
        }

        // Editable + value?
        if ($this->Tag->editable() && $value) {
            // return the user's value
            return $this->unique_slug($value);
        }

        if ($this->Tag->for()) {

            $parts = $this->break_for_string($this->Tag->for());
            if (PerchUtil::count($parts)) {
                $str = array();
                foreach($parts as $part) {
                    if (isset($post[$part])) {
                        $str[] = trim(PerchUtil::safe_stripslashes($post[$part]));
                    }
                }
                return $this->unique_slug(PerchUtil::urlify(implode(' ', $str)));
            }

            if (isset($post[$this->Tag->for()])) {
                return $this->unique_slug(PerchUtil::urlify(trim(PerchUtil::safe_stripslashes($post[$this->Tag->for()]))));
            }
        }

        return '';
    }

    public function get_search_text($raw=false)
    {
        if ($raw===false) $raw = $this->get_raw();

        $parts = explode('-', $raw);
        return implode(' ', $parts);
    }

    private function break_for_string($for)
    {
        return explode(' ', $for);
    }

    private function unique_slug($slug)
    {
        // Get existing slugs
        $collection = $this->Tag->collection();

        if(!isset($collection) || $collection == '')
        {
            return $slug;
        }

        $DB = PerchDB::fetch();     
        $collection_info = $DB->get_row('SELECT * FROM perch2_collections c WHERE c.collectionKey = '. $DB->pdb($collection));

        $collection_id = $collection_info['collectionID'];

        if(!isset($collection_id) || !is_numeric($collection_id))
        {
            return $slug;
        }

        $id = $this->Tag->id();


        // Need to exclude the current item
        $item_id = $_GET['itm'];

        $item_sql = '';
        if(isset($item_id) && is_numeric($item_id))
        {
            $item_sql = " AND i.itemID <> $item_id";
        }

        // All items from the colection
        $items = $DB->get_rows('SELECT indexValue FROM perch2_collection_index i, perch2_collection_revisions r WHERE r.itemID = i.itemID AND i.itemRev = r.itemLatestRev AND i.collectionID = '.$DB->pdb($collection_id). ' AND i.indexKey= '.$DB->pdb($id) . $item_sql);

        if($items)
        {
            // Build an array of slugs
            foreach($items as $item)
            {
                $slugs[] = $item['indexValue'];
            }

            // Is our proposed slug in the list?  If yes, return it
            if( in_array($slug, $slugs))
            {
                $try = $slug;
                $i = 1; 
                while(in_array($try, $slugs))
                {
                    $try = $slug.'-'.$i;
                    $i++;
                }

                return $try;
            }
            else
            {
                return $slug;           
            }
        }
        else
        {
            return $slug;   
        }
    }
}
?>