CodeIgniter Forums
PyroCMS v0.9.7.4 - an open-source modular general purpose CMS - Printable Version

+- CodeIgniter Forums (https://forum.codeigniter.com)
+-- Forum: Archived Discussions (https://forum.codeigniter.com/forumdisplay.php?fid=20)
+--- Forum: Archived Libraries & Helpers (https://forum.codeigniter.com/forumdisplay.php?fid=22)
+--- Thread: PyroCMS v0.9.7.4 - an open-source modular general purpose CMS (/showthread.php?tid=17376)



PyroCMS v0.9.7.4 - an open-source modular general purpose CMS - El Forum - 08-24-2009

[eluser]Turv[/eluser]
I think i have a possible solution i'm working on now. Will post back shortly.


PyroCMS v0.9.7.4 - an open-source modular general purpose CMS - El Forum - 08-24-2009

[eluser]Turv[/eluser]
Okay, Well i hit a stump...

I've got a solution that will allow an unlimited sub-page structure so you can have pages such as

/parent/primary/secondary/sub-page/

It works by passing an array of URI Segments to the page function, This receives an array such as

Code:
Array
(
    [1] => parent
    [2] => primary
    [3] => secondary
    [4] => sub-page
)

With sub-page being the targetting page for content, I store this slug in a variable, remove it from the array. The last item in the array (Secondary) is then the parent of this page. So i query the database to get the ID (Parent ID) for the Secondary page.

If there are more than one result, I perform the final query where i find a row in the database where the slug is 'sub-page' and the parent id is whatever the id of secondary is.

This means you can have multiple 'sub-page' slugs.

However...The problem i have is that if i have pages where the last parts are identical (unlikley to happen?) it causes problems. Such as, If i have another page such as...

Code:
Array
(
    [1] => test-page
    [2] => other
    [3] => secondary
    [4] => sub-page
)

Then with the last two parts (secondary/sub-page) being the same it fails. What i need is a way to recursively call getIdBySlug passing a new parent slug until i get a reslt with only one row.

This is the code i have...

Pages Controller, _remap function
Change: If a sub page (of any kind) exists then pass on the uri segment array, otherwise just pass the first segment, call page function directly
Code:
function _remap()
    {
        // If Sub page exsits
        if($this->uri->segment(2)) {
            $slug = $this->uri->segment_array();
        } else {
            $slug = $this->uri->segment(1, 'home');
        }
        
        // This basically keeps links to /home always pointing to the actual homepage even when the default_controller is changed
        @include(APPPATH.'/config/routes.php'); // simple hack to get the default_controller, could find another way.
        
        // The default route is set to a different module than pages. Send them to there if they come looking for the homepage
        if(!empty($route) && $slug == 'home' && $route['default_controller'] != 'pages')
        {
            redirect('');
        }
        
        // Default route = pages
        else
        {
            // Show the requested page with all segments available
            //call_user_func_array(array($this, 'page'), $slug);
            $this->page($slug);
        }
    }

Page Controller, Pages function
Change: Restored back to the original, No changes needed in this function anymore

Pages Model, getBySlug function
Code:
public function getBySlug($slug = '', $lang = NULL)
    {
        /**
         * Slug is now an Array
         * Take the Last element as slug
         * Take the element before that for parent id
         * Perform database query
         */
         if(is_array($slug)) {
             // Get Last Element of Slug aray
             $Page = end($slug);
            
             // Remove last element from Slug array
             array_pop($slug);

             // The new last element will be used for the parent ID
            $ParentID = $this->getIdBySlug(end($slug), $Page);

             $this->db->where('slug', $Page);
             $this->db->where('parent', $ParentID);
         }
        
         // If $slug is not an array, must be single page, just use it directly
         else
         {
            $this->db->where('slug', $slug);
         }
        
        if($lang == 'all')
        {
            exit('where did this code go?! tell me if you see this message [email protected]!');
        }  
            
        elseif($lang != NULL)
        {
            $this->db->where('lang', $lang);
        }
        
        return $this->get($lang);
    }

Pages Model, getIdBySlug function
Code:
public function getIdBySlug($parent = null, $slug = null) {
        if($parent == null || $slug == null)
            return false;
        
        $this->db->where('slug', $parent);
        $Query = $this->db->get('pages');
        
        // Store Result - Continue
        $Result = $Query->result_array();

        // If there is more than one slug exists of the same name, perform additional checks
        if($Query->num_rows() > 1)
        {
            foreach ($Result as $Row) {
    
                $this->db->where('slug', $slug);
                $this->db->where('parent', $Row['id']);
                $Query = $this->db->get('pages');
                
                // If there any results then return the ID
                if($Query->num_rows() > 0)
                    return $Row['id'];
            }
        }    
        // Only one result, return Id
        else
        {
            return $Result[0]['id'];
        }
    }

So basically, what i did in my test installation was create a number of child pages so i had two urls as below

/primary/sub-page/sub-sub-page/
/test-page/sub-page/sub-sub-page/

When going to /test-page/sub-page/sub-sub-page/ i received the content for /primary/sub-page/sub-sub-page/ because in my getIdBySlug function, when doing the additional check i return the id as soon as i find a match. What i need to do instead is like a recursive function using the rest of the uri array to....I don't know, have a look see if you can figure anything out.

This at the moment...seems to work perfectly for any url as long as there are not two segments of the same name as per the above example


PyroCMS v0.9.7.4 - an open-source modular general purpose CMS - El Forum - 08-25-2009

[eluser]ray73864[/eluser]
One thing you could do, is get the entire ancestry for 'sub-sub-page', starting with the parent 'primary' or 'test-page' and working your way through the chain, get the id for 'test-page' then the id for 'sub-page' then for 'sub-sub-page'.

Once you have the full ancestry then you will be assured that you have the correct page the person is asking for.


PyroCMS v0.9.7.4 - an open-source modular general purpose CMS - El Forum - 08-25-2009

[eluser]Turv[/eluser]
[quote author="ray73864" date="1251209638"]One thing you could do, is get the entire ancestry for 'sub-sub-page', starting with the parent 'primary' or 'test-page' and working your way through the chain, get the id for 'test-page' then the id for 'sub-page' then for 'sub-sub-page'.

Once you have the full ancestry then you will be assured that you have the correct page the person is asking for.[/quote]

Yeah that's what i was thinking, I mean the above solution works just fine for 99% of scenarious i mean on content pages the likelyhood you are going to have a sub page, and sub sub page of the same value is very unlikely as if you are, chances are it should be a module like product/details.

That's why i thought about doing some form of recursive function, I mean i have the array of 'slugs' at hand, I just need to go through each one to verify the correct parent ID, If i can get the correct Parent ID I can perform the query and retreive the correct content and it will work just fine.


PyroCMS v0.9.7.4 - an open-source modular general purpose CMS - El Forum - 08-25-2009

[eluser]Phil Sturgeon[/eluser]
Code:
exit('where did this code go?! tell me if you see this message [email protected]!');

Ha! Brilliant...

Recursive is correct. This needs to loop through all segments in the URL to find the correct child. We cannot simply assume the same child wont exist in two places, as easy as that might make development.

Turv, while you work on this if the Language element of the page manager gets in your way please feel free to delete ALL of it, no mercy. That feature is screwed and needs to be disabled anyway.


PyroCMS v0.9.7.4 - an open-source modular general purpose CMS - El Forum - 08-25-2009

[eluser]Phil Sturgeon[/eluser]
A little something like this?

Code:
$last_slug = array_pop($url_segments);

$this->db->from('pages p1');

$i = 1;
foreach( $url_segments as $slug )
{
    $current_alias = 'p'.$i);
    $parent_alias = 'p'.$i + 1;
    
    $this->db->join('pages '.$current_alias, $parent_alias.'.parent_id = '.$current_alias.'.id AND '.$parent_alias.'.slug = "'.$slug.'"');
    $i++;
}

$this->db->where('p1.slug', $last_slug);

Something like this might start us in the right direction.

Basically looks to the slug as unique then chases up the tree using self joins, each way checking the slug matches so we should get the right tree even if there are multiple children with matching names.

Untested mind. Anyone free to check it out?

Update: This is a implementation of the theory shown here.


PyroCMS v0.9.7.4 - an open-source modular general purpose CMS - El Forum - 08-25-2009

[eluser]Phil Sturgeon[/eluser]
OK scratch that, I was close but not entirely.

The magic query we need to use is this:

Quote:SELECT p3.*
FROM pages AS p1
LEFT JOIN pages AS p2 ON p2.parent = p1.id
LEFT JOIN pages AS p3 ON p3.parent = p2.id
WHERE p1.slug = 'parent' AND p2.slug = 'child' AND p3.slug = 'grandchild';

This will support everything we need from it. It will pick the correct tree and make sure even if there are two "child" or "grandchild" slugs anywhere else it will still take the right path.

I have roughed up some code (not tested).

Code:
// Work out how many segments there are
$total_segments = count($url_segments);

// Which is the target alias (the final page in the tree)
$target_alias = 'p'.$total_segments;

// Select everything (*) for the target alias
$this->db->select($target_alias.'.*')->from('pages p1');

$i = 1;
foreach( $url_segments as $slug )
{
    // Current is the current page, child is the next page to join on.
    $current_alias = 'p'.$i);
    $child_alias = 'p'.$i + 1;
    
    // We dont want to join the first page again
    if($i > 1)
    {
        $this->db->join('pages '.$current_alias, $child_alias.'.parent = '.$current_alias.'.id');
    }
    
    // Add slug to where clause to keep us on the right tree
    $this->db->where($current_alias . '.slug', $slug);
    
    $i++;
}

$query = $this->db->get();

Something like that should do. Sudden inspiration! :lol:


PyroCMS v0.9.7.4 - an open-source modular general purpose CMS - El Forum - 08-25-2009

[eluser]Turv[/eluser]
[quote author="Phil Sturgeon" date="1251226630"]OK scratch that, I was close but not entirely.

The magic query we need to use is this:

Quote:SELECT p3.*
FROM pages AS p1
LEFT JOIN pages AS p2 ON p2.parent = p1.id
LEFT JOIN pages AS p3 ON p3.parent = p2.id
WHERE p1.slug = 'parent' AND p2.slug = 'child' AND p3.slug = 'grandchild';

This will support everything we need from it. It will pick the correct tree and make sure even if there are two "child" or "grandchild" slugs anywhere else it will still take the right path.

I have roughed up some code (not tested).

Code:
// Work out how many segments there are
$total_segments = count($url_segments);

// Which is the target alias (the final page in the tree)
$target_alias = 'p'.$total_segments;

// Select everything (*) for the target alias
$this->db->select($target_alias.'.*')->from('pages p1');

$i = 1;
foreach( $url_segments as $slug )
{
    // Current is the current page, child is the next page to join on.
    $current_alias = 'p'.$i);
    $child_alias = 'p'.$i + 1;
    
    // We dont want to join the first page again
    if($i > 1)
    {
        $this->db->join('pages '.$current_alias, $child_alias.'.parent = '.$current_alias.'.id');
    }
    
    // Add slug to where clause to keep us on the right tree
    $this->db->where($current_alias . '.slug', $slug);
    
    $i++;
}

$query = $this->db->get();

Something like that should do. Sudden inspiration! :lol:[/quote]

Fantastic! Got it working!

This is now my getBySlug function

Code:
public function getBySlug($slug = '', $lang = NULL)
    {
        /**
         * Slug is now an Array
         * Perform Query to determine correct Page ID
         */
         if(is_array($slug)) {
            // Count Total Items in Array
            $TotalSlugs = count($slug);
            // Set Target Alias
            $TargetAlias = 'p'.$TotalSlugs;

            // Start Query, Select (*) from Target Alias, from Pages
            $this->db->select($TargetAlias.'.*');
            $this->db->from('pages AS p1');
            
            // Set the Limit, Can only be one result!
            $this->db->limit(1);
            
            // Initialise Counter
            $i = 1;
            
            // Loop thorugh each Slug
            foreach( $slug as $page ) {
            
                // Current is the current page, child is the next page to join on.
                $CurrentAlias = 'p'.$i;
                $ChildAlias = 'p'.($i - 1);
        
                // We dont want to join the first page again
                if($i > 1)
                    $this->db->join('pages AS '.$CurrentAlias, $CurrentAlias.'.parent = '.$ChildAlias.'.id', 'left');
    
                // Add slug to where clause to keep us on the right tree
                $this->db->where($CurrentAlias . '.slug', $page);
        
                // Increment
                $i++;
            }
         }
         // If $slug is not an array, must be single page, just use it directly
         else
         {
            $this->db->where('slug', $slug);
         }
        
        if($lang == 'all')
        {
            exit('where did this code go?! tell me if you see this message [email protected]!');
        }  
            
        elseif($lang != NULL)
        {
            $this->db->where( (is_array($slug) ? $TargetAlias.'.lang' : 'lang'), $lang);
        }
        
        return $this->get($lang);
    }

There is one problem with this code it seems, and it's the From clause. This is the output query of the above (using my test)

Code:
SELECT p3.*
FROM (pages AS p1, pages)
LEFT JOIN pages AS p2 ON p2.parent = p1.id
LEFT JOIN pages AS p3 ON p3.parent = p2.id
WHERE `p1`.`slug` = 'test-page'
AND `p2`.`slug` = 'sub-page'
AND `p3`.`slug` = 'sub-sub-page'
AND `p3`.`lang` = 'en'
LIMIT 1

This produced 10 results apparently, Hence the limit clause. However changing the from clause to this makes it work perfectly.

Note: The only difference is the from clause is 'FROM pages as p1' where as CI produces FROM (pages AS p1, pages). Why is it doing that, and can we make it produce what we want?

Code:
SELECT p3.*
FROM pages AS p1
LEFT JOIN pages AS p2 ON p2.parent = p1.id
LEFT JOIN pages AS p3 ON p3.parent = p2.id
WHERE `p1`.`slug` = 'test-page'
AND `p2`.`slug` = 'sub-page'
AND `p3`.`slug` = 'sub-sub-page'
AND `p3`.`lang` = 'en'
LIMIT 1

Great job bud, I would have never thought about doing that.


PyroCMS v0.9.7.4 - an open-source modular general purpose CMS - El Forum - 08-25-2009

[eluser]Phil Sturgeon[/eluser]
Sweet, glad my code was helpful!

The problem is that getBySlug() currently just sets up the db clauses then runs through $this->get() to grab the content. This whole model looks bloody disgusting and really needs some cleaning!

A few things here.

I would take out the AS commands, they are not needed and might screw with things a bit.

It is not a LEFT join, as neither side is any more likely to exit. It is closest to being an outter join, but the WHERE clause will destroy it anyway, so leave the 3rd param in the join blank.

We should change this model now as there is no longer any need to ever get a page by a slug. It will always be edited, viewed or referenced by an ID or the uri segments.

Here is a suggestion of how the model should probably look now. This replaces getById(), getBySlug() and get() with a modified getById() and a renamed function getByURL(). get() is deleted.

You might also notice me being picky with your syntax. It is partially me being anal and partially me wanting to keep the CMS using the same sytax all over. camelCase for functions, underscores for variables, allman for structures.

Other than that, good work dude! :-)


PyroCMS v0.9.7.4 - an open-source modular general purpose CMS - El Forum - 08-25-2009

[eluser]sigork[/eluser]
What is the license of PyroCMS?

GNU GPL v3? Creative Commons (by-nc-nd)? ...

Thanks.