Skip to content

Commit

Permalink
Major update for pages.
Browse files Browse the repository at this point in the history
Better deletion management and cascading.
Added a PageTranslation class to manage (future) translations easily.
Added a "getTree()" method in the Page entity, to get the page parents'
tree with a string separator (used to generate urls)
  • Loading branch information
Pierstoval committed Mar 2, 2015
1 parent 879a343 commit 63650be
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 22 deletions.
3 changes: 1 addition & 2 deletions Controller/FrontController.php
Expand Up @@ -10,13 +10,11 @@

namespace Pierstoval\Bundle\CmsBundle\Controller;

use Doctrine\ORM\NonUniqueResultException;
use Pierstoval\Bundle\CmsBundle\Entity\Page;
use Pierstoval\Bundle\CmsBundle\Repository\PageRepository;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class FrontController extends Controller
{
Expand Down Expand Up @@ -63,6 +61,7 @@ protected function getHomepage($host = null)
}
throw new \Exception('No homepage has been configured. Please check your existing pages or create a homepage in your backoffice.');
}

/**
* @param array $slugs
* @param Page[] $pages
Expand Down
109 changes: 91 additions & 18 deletions Entity/Page.php
Expand Up @@ -10,7 +10,9 @@

namespace Pierstoval\Bundle\CmsBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Gedmo\Blameable\Traits\BlameableEntity;
use Gedmo\IpTraceable\Traits\IpTraceableEntity;
use Gedmo\Mapping\Annotation as Gedmo;
Expand All @@ -23,7 +25,9 @@
* @ORM\Entity(repositoryClass="Pierstoval\Bundle\CmsBundle\Repository\PageRepository")
* @ORM\Table(name="pierstoval_cms_pages")
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
* @Gedmo\TranslationEntity(class="Pierstoval\Bundle\CmsBundle\Entity\PageTranslation")
* @UniqueEntity("slug")
* @ORM\HasLifecycleCallbacks()
*/
class Page
{
Expand Down Expand Up @@ -52,7 +56,6 @@ class Page

/**
* @var string
* @Gedmo\Translatable
* @Gedmo\Slug(fields={"title"})
* @ORM\Column(name="slug", type="string", length=255, unique=true)
* @Assert\Length(max=255)
Expand Down Expand Up @@ -125,8 +128,8 @@ class Page

/**
* @var Page
* @ORM\ManyToOne(targetEntity="Pierstoval\Bundle\CmsBundle\Entity\Page", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
* @ORM\ManyToOne(targetEntity="Pierstoval\Bundle\CmsBundle\Entity\Page", inversedBy="children", fetch="EAGER")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="cascade")
*/
protected $parent;

Expand All @@ -136,21 +139,32 @@ class Page
*/
protected $children;

/**
* @var PageTranslation[]|ArrayCollection
* @ORM\OneToMany(targetEntity="PageTranslation", mappedBy="object", cascade={"persist", "remove"})
*/
private $translations;

public function __toString()
{
return $this->title;
}

public function __construct()
{
$this->translations = new ArrayCollection();
}

/**
* @return mixed
* @return string
*/
public function getTitle()
{
return $this->title;
}

/**
* @param mixed $title
* @param string $title
*
* @return Page
*/
Expand All @@ -161,15 +175,15 @@ public function setTitle($title)
}

/**
* @return mixed
* @return string
*/
public function getSlug()
{
return $this->slug;
}

/**
* @param mixed $slug
* @param string $slug
*
* @return Page
*/
Expand Down Expand Up @@ -237,15 +251,15 @@ public function setMetaTitle($metaTitle)
}

/**
* @return mixed
* @return string
*/
public function getMetaKeywords()
{
return $this->metaKeywords;
}

/**
* @param mixed $metaKeywords
* @param string $metaKeywords
*
* @return Page
*/
Expand Down Expand Up @@ -275,15 +289,15 @@ public function setCategory($category)
}

/**
* @return mixed
* @return string
*/
public function getCss()
{
return $this->css;
}

/**
* @param mixed $css
* @param string $css
*
* @return Page
*/
Expand All @@ -294,15 +308,15 @@ public function setCss($css)
}

/**
* @return mixed
* @return string
*/
public function getJs()
{
return $this->js;
}

/**
* @param mixed $js
* @param string $js
*
* @return Page
*/
Expand Down Expand Up @@ -332,21 +346,23 @@ public function setEnabled($enabled)
}

/**
* @return mixed
* @return Page
*/
public function getParent()
{
return $this->parent;
}

/**
* @param mixed $parent
* @param Page $parent
*
* @return Page
*/
public function setParent(Page $parent)
public function setParent(Page $parent = null)
{
if ($parent->getId() == $this->id) {
if ($parent && $parent->getId() == $this->id) {
// Refuse the page to have itself as parent
$this->parent = null;
return $this;
}
$this->parent = $parent;
Expand All @@ -362,7 +378,7 @@ public function getId()
}

/**
* @return mixed
* @return Page[]
*/
public function getChildren()
{
Expand Down Expand Up @@ -418,4 +434,61 @@ public function setHost($host)
return $this;
}

/**
* @return PageTranslation[]
*/
public function getTranslations()
{
return $this->translations;
}

/**
* @param PageTranslation $t
* @return Page
*/
public function addTranslation(PageTranslation $t)
{
if (!$this->translations->contains($t)) {
$this->translations[] = $t;
$t->setObject($this);
}
return $this;
}

public function getTree($separator = '/')
{
$tree = '';

$current = $this;
do {
$tree = $current->getSlug().$separator.$tree;
$current = $current->getParent();
} while ($current);

return trim($tree, $separator);
}

/**
* @ORM\PreRemove()
* @param LifecycleEventArgs $event
*/
public function onRemove(LifecycleEventArgs $event)
{
$om = $event->getObjectManager();
foreach ($this->translations as $translation) {
$om->remove($translation);
}
$om->flush();
foreach ($this->children as $child) {
$child->setParent(null);
$om->persist($child);
}
$this->enabled = false;
$this->parent = null;
$this->title .= '-'.$this->id.'-deleted';
$this->slug .= '-'.$this->id.'-deleted';
$om->persist($this);
$om->flush();
}

}
49 changes: 49 additions & 0 deletions Entity/PageTranslation.php
@@ -0,0 +1,49 @@
<?php
/*
* This file is part of the PierstovalCmsBundle package.
*
* (c) Alexandre "Pierstoval" Rock Ancelet <pierstoval@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Pierstoval\Bundle\CmsBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Translatable\Entity\MappedSuperclass\AbstractPersonalTranslation;

/**
* @ORM\Entity
* @ORM\Table(name="pierstoval_cms_pages_translations",
* uniqueConstraints={
* @ORM\UniqueConstraint(name="lookup_unique_idx", columns={
* "locale", "object_id", "field"
* })
* }
* )
*/
class PageTranslation extends AbstractPersonalTranslation
{

/**
* Convenient constructor
*
* @param string $locale
* @param string $field
* @param string $value
*/
public function __construct($locale, $field, $value)
{
$this->setLocale($locale);
$this->setField($field);
$this->setContent($value);
}

/**
* @ORM\ManyToOne(targetEntity="Page", inversedBy="translations")
* @ORM\JoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $object;

}
38 changes: 36 additions & 2 deletions README.md
Expand Up @@ -145,15 +145,15 @@ services:

## Usage

Simply go to your backoffice in `http://127.0.0.1/admin`, and login if you are using the `Security` component.
Simply go to your backoffice in `/admin`, and login if you are using the `Security` component.

You can manage `Cms Pages` and `Cms Categories` as you wish, like in an usual backoffice.

The `FrontController` handles some methods to view pages with a single `indexAction()`.

The URI for a classic page is simply `/{slug}` where `slug` is the... page slug (wow, thanks captain hindsight!).

If your page has one `parent`, then the URI is the following: `/{parentSlug}/{slug}`. As the slugs are verbose nough,
If your page has one `parent`, then the URI is the following: `/{parentSlug}/{slug}`. As the slugs are verbose enough,
you can notice that we respect the pages hierarchy in the generated url.
You can navigate through a complex list of pages, as long as they're related as `parent` and `child`.
This allows you to have such urls like this one :
Expand All @@ -162,6 +162,40 @@ a parent, that has a parent, and so on, until you reach the "root" parent.
** Note: this behavior is the precise reason why you have to use a specific prefix for your `FrontController` routing
import, unless you may have many "404" errors.**

### Generate a route based on a single page

If you have a `Page` object in a view or in a controller, you can get the whole arborescence by using the `getTree()`
method, which will navigate through all parents and return a string based on a separator argument (default `/`, for urls).

Let's get an example with this kind of tree:

```
/ - Home (root url)
├─ /welcome - Welcome page (set as "homepage", so "Home" will be the same)
│ ├─ /welcome/our-company - Our company
│ ├─ /welcome/our-company/financial - Financial
│ └─ /welcome/our-company/team - Team
└─ Contact
```

Imagine we want to generate the url for the "Team" page. You have this `Page` object in your view/controller.

```twig
{# Page : "Team" #}
{{ path('cms_home', {"slugs": page.tree}) }}
{# Will show : /welcome/our-company/team #}
```

Or in a controller:

```php
// Page : "Team"
$url = $this->generateUrl('cms_home', array('slugs' => $page->getTree()));
// $url === /welcome/our-company/team
```

With this, you have a functional tree system for your CMS!

## <a name="fosuserbundle"></a> Using FOSUserBundle to have a secured backoffice

If want to use `FOSUserBundle`, you have to setup the bundle by reading [FOSUserBundle's documentation](https://github.com/FriendsOfSymfony/FOSUserBundle#documentation).
Expand Down

0 comments on commit 63650be

Please sign in to comment.