Parents, Collection Types

Permalink
In the glossary and other parts of the documentation there are explanations of how Pages relate to Collections and Blocks and Attributes, but it would be nice if these explanations were interspersed with code snippets:

$parent = Page::getByID($this->post('cParentID'));


When I see the above line, I think "What is a parent? Why am I trying to get the parent?"

And when I see this line:

if ($p->getCollectionParentID() != $parent->getCollectionID()) {
               $p->move($parent);
            }


I think, "where is this getting moved to??".

Or if I see something like this:
$faqSectionList = new PageList();
$faqSectionList->filterByFaqSection(1);


I think, "Isn't a method like filterByFaqSection specific to this list object? How is that happening?"

And the last thing:

$blocks = $p->getBlocks('Main');
      foreach($blocks as $b) {
         $b->deleteBlock();
      }


I ask myself, "What is 'Main'? Is that all the blocks on the page? Why do I need to delete all the blocks first, doesn't that wipe out the content on my page?"

Anyhow, I am sure if you are experienced with concrete5 a lot of these questions I have seem trivial, but it occurs to me that if I have these questions, other people who are new to the concrete5 framework probably have these questions too.

View Replies: View Best Answer
szucslaszlo replied on at Permalink Reply
Hey there!
You are right, many of these question arise in the minds of everyone, when they start studying C5 code to really understand, what is going on here - including myself. I hope, that my knowledge so far would be enough for you to get a little closer. Unfortunatelly, I do not knoe anything of your previous programming experiences and education, so I shall have to assume the worst, that you are unfamiliar with object-oriented programming. Please do not lose heart if at some point you feel that I am talking about something that you do not understand yet - it is possible, that I will make things clear just in the next sentence. :) So, here we go:

$parent = Page::getByID($this->post('cParentID'));


It is difficult to explain the exact meaning of this line, without knowing that in which class it is located. This is because $this is a special variable, which has different meanings depending on the context. In general, we can tell, that this code puts a Page object into the variable named $parent. We know this, because Page::getByID( $id ) is a function that returns a Page object - the page that has the unique ID given as it's sole argument. The argument at hand is $this->post('cParentID'). It seems that this function would somehow yield an integer, which is actually the collection id of some page's parent. But what in the world is a parent? Well, without giving a lecture in graph theory, let's have a look at the sitemap. Notice, that the way the site structure is shown resembles a tree - a tree turned upsidedown actually. The pages (nodes) are in a hierarchical relation with each other. Given a page, the one that is immidietally above is it's parent. The page just beneath it is it's child. The pages, that can be 'reached' by traversing only 'straight' upwards from a given page are it's ancestors, and all pages that are the children of any of it's children, are it's predecessors. A page can have siblings, which are are all children of it's parent, not including itself. To our greatest disappointment, this the end of excitement: you don't need to make two pages have sex, before having children. In fact, pages do not even have a gender! Because of this, a page can have an arbitrary number of children, but can only have one parent at most. There is only one page without a parent, and it is called to root. This is actually the Home page at a default install, and it's collection id is always 1.

if ($p->getCollectionParentID() != $parent->getCollectionID() ){
    $p->move( $parent );
}


Assuming, that the variables $p and $parent both hold Page objects, the code above gets the parent's collection id of each Page's parent and tests if they are different. If they are, it moves the Page object stored in $p underneath the Page stored in $parent. Think of this as a kind of adoption of $p by $parent.

$faqSectionList = new PageList();
$faqSectionList->filterByFaqSection(1);


As for this code, I neither understand the questions, nor find the mention method in any classes for further information.

$blocks = $p->getBlocks('Main');
    foreach($blocks as $b){
        $b->deleteBlock();
    }


This code would also need some more detailed information on it's whereabouts, but what it generally does is that it loops through all the Block items contained in the Area named 'Main' and deletes them. This would delete only the blocks that were in the 'Main' area. 'Main' is just a name, it could be called anything else you like - as long as you keep in mind some basic naming conventions, of course. If you do a clean install of C5, you will notice, that all the default page types have an area named 'Main'.

Finally, some general thing you should know about collection, pages, blocks and attributes: collections are general objects, whereas pages are a more specific type of collections. What this means is, that a page retains some aspects of a collection, but can be (and actually is) very different in some ways. This means, that you can do certain things with a page, which you can not do with a collection. Suppose you are a vegetarian. If I put in fron of you a nice looking meal, you may ask what is that. Before knowing if you can eat it (that is, knowing if the 'meal' object has an 'eatThis' method) you must know if it is made of vegetables. Suppose that you do not like spinach. Now if you would want to know if this is for you, what would you do? Well certainly, you would start interrogating the vegetables, one-by-one, about what kind of vegetables they are - but seriously, what you would do is, loop through the 'vegatable' objects and check the return value if their 'getName' method! Should one of them yield 'spinach', you know that you can not eat this dish - you exit the loop. Notice, that all the vegetables have a method for inquiring their names. This is a common feature, but for each different kind of vegetable, this method would return a different value. This is because spinach, carrot, lattice, tomato, etc... they are generally vegetables, but each of them is a more specific kind of vegetable - just like pages are to collections. This is called generalization/specialization or in some contexts inheritance - because the specific kinds of vegetables inherit some traits from the general vegetable class.

Blocks: blocks are the basic content elements that make up your site. They can be placed on a page by two means - through the C5 interface, or hard coded into it. When you place a block through the interface, you must specify an Area, where you would like to place it. A page can have an arbitrary number of Areas specified, where you can put blocks. Hard coding a block can be useful, for example when you want to include a navigation menu on every page, without haveing to manually add it through the C5 interface. Blocks are also a kind of generalized objects, where as Autonav, Image, Content, Pagelist, etc... are more specific kinds of blocks with different extra features.

Page attributes come in a handful of types. They can be used to lot's of different things, but their basic purpose is to store information about a page. Suppose you have a list of questions about a page. The answer to this page can be understood as an image, a boolean value, a string, etc... for example: "What is the header image of this page?", "Should this page be included in lists generated by the autonav block?", "What should be the quote displayed on this page?". The answor to these questions - from the point of view of C5 could an image, a boolean value and a string respectively. Notice, the second question. You will eventually face the problem - if you haven't before - that when you give the "Exclude form nav" attribute a true value for a page, it's children are still included in the autonav list (usually at some erroneus place as another page's children). Such behaviour is always because of your incompetence - ofcourse. The scope of this attribute is solely the page for which it is set, not it's children. How you deal with it's children, based upon this attribute is another thing. Because the inner workings of the autonav block, this attribute alone is not enough to 'hide' part of you site from the autonav block.

I don't know how do you feel about studying the database tables of C5, but I recommend that you invest time in it - it has helped me a lot, to understand how things work. Also, if you would not be using an integrated developement environment, I urge you to do so. It's features help to get a grip on how the different objects are related to each other. I hope that my little tutorial got you a little closer to your goals with C5. It is worth spending time learning and understanding it, as it is the only CMS I know of, that effectively serves it's purpose: to make content management easy and fast. So, I guess this is goodbye. Have a nice day!
nicolechung replied on at Permalink Reply
Thank you for the very detailed explanation.

I do have a few more questions about the code I posted, here is some more detail.

Most of my questions come from the FAQ tutorial, in particular, the controller class:

http://www.concrete5.org/marketplace/addons/example-faq/...

protected function loadFAQSections() {
      $faqSectionList = new PageList();
      $faqSectionList->filterByFaqSection(1);
      $faqSectionList->sortBy('cvName', 'asc');
      $tmpSections = $faqSectionList->get();
      $sections = array();
      foreach($tmpSections as $_c) {
         $sections[$_c->getCollectionID()] = $_c->getCollectionName();
      }
      $this->set('sections', $sections);
   }


Here is my first question with the code above.
I thought $faqSectionList is an instance of PageList (since the first line says...new PageList();). Then how does PageList (which I assume is a general class) get a specific sounding method like "filterByFaqSection"? Is that just a built in feature of the PageList or is it because I made an attribute called "faq_section"?

private function saveData($p) {
      $blocks = $p->getBlocks('Main');
      foreach($blocks as $b) {
         $b->deleteBlock();
      }
      $bt = BlockType::getByHandle('content');
      $data = array('content' => $this->post('faqBody'));
      $p->addBlock($bt, 'Main', $data);
      Loader::model("attribute/categories/collection");
      $cak = CollectionAttributeKey::getByHandle('faq_tags');
      $cak->saveAttributeForm($p);
   }


Here is my second question that relates to the next bit of code (above).

In the form, there is a textarea 'faqBody'...so it makes sense to save that in the page data in this "save" method. But I still don't quite understand why the other blocks are getting deleted before the save...? This part I am really hazy on.
xaritas replied on at Permalink Best Answer Reply
You're right that PageList has a magic method that converts undefined filterBy calls. From the source:
/* magic method for filtering by page attributes. */
   public function __call($nm, $a) {
      if (substr($nm, 0, 8) == 'filterBy') {
         $txt = Loader::helper('text');
         $attrib = $txt->uncamelcase(substr($nm, 8));
         if (count($a) == 2) {
            $this->filterByAttribute($attrib, $a[0], $a[1]);
         } else {
            $this->filterByAttribute($attrib, $a[0]);
         }
      }         
   }


As for the second, I haven't studied the code, but I believe the way the FAQ block works is by creating a Page whose sole purpose is to hold the contents of the FAQ text. In that case, there shouldn't be anything else there, so deleting the previous content is fine--and if you don't delete it, you'll get both versions on the page. Why does it delete instead of updating? I dunno. Try installing the code and putting some instrumentation in, and watch the database. Make some changes based on your questions and watch the results again. Experiment.

Generally I have found that C5 supports most things I want to do, but you have to be willing to read the source code.

Also, keep in mind that the block you're looking at targeted an earlier version of C5. It's entirely possible that there is a different way of doing things.
nicolechung replied on at Permalink Reply
Thank you soooo much! That explains a lot.