2

I need to go through all CMS pages and CMS blocks to update something, so I'm loading a collection for both of them.

After doing stuff to their content I try to save the individual items. They get saved, but all records in the store tables are removed afterwards and the pages and blocks are not associated with any stores anymore.

How can I do this and keep the stores intact?

For pages:

$pages = Mage::getResourceModel('cms/page_collection')
    ->addFieldToFilter('content', array('like' => '%something wrong%'));
foreach ($pages as $page) {
    /* @var $page Mage_Cms_Model_Page */
    $page->setContent(
        str_replace('something wrong', 'something right', $page->getContent())
    )->save();
}
1
  • I could not find anything like this out there on the internet, so I'll post this here and answer it myself for future reference
    – 7ochem
    Apr 14, 2015 at 13:57

2 Answers 2

3

I got it! The CMS page and block resource models are checking the associated store ID's after save. There are no store ID's on the loaded model, so it will remove all records for this page/block from the stores table.

Forward compatible solution:

As remarked by comment, the below solution is not in a forward compatible way if the models and it's relations (or data storage) would change in the future.

This would be a more forward compatible way (and simpler ;)):

$pageIds = Mage::getResourceModel('cms/page_collection')
    ->addFieldToFilter('content', array('like' => '%something wrong%'));
/* @var $pageIds Mage_Cms_Model_Resource_Page_Collection */

foreach ($pageIds->getAllIds() as $pageId) {
    Mage::getModel('cms/page')->load($pageId)
        ->setContent(
            str_replace('something wrong', 'something right', $page->getContent())
        )->save();
}

Solution non-standard way:

Right now, for example for CMS pages, I'm joining the cms_page_store table to get all associated store ID's. So upon save, the page resource model won't detect any changes in associated stores.

The trick to get all associated store ID's (comma separated) is in using MySQL's GROUP_CONCAT().

$pages = Mage::getResourceModel('cms/page_collection');
$pages->addFieldToFilter('content', array('like' => '%something wrong%'))
    ->getSelect()->join(
        array('store_table' => $pages->getTable('cms/page_store')),
        'main_table.page_id = store_table.page_id',
        array(
            'stores' => new Zend_Db_Expr('GROUP_CONCAT(store_table.store_id)')
        )
    )->group('main_table.page_id');

foreach ($pages as $page) {
    /* @var $page Mage_Cms_Model_Page */
    $page->setStores(explode(',', $page->getStores()))
        ->setContent(
            str_replace('something wrong', 'something right', $page->getContent())
        )->save();
}
1
  • You almost got it, but are going about it in a non-standard way. Since this isn't something you'd have to worry about optimizing for performance, simply call ->load() on each collection member. The stores are filled for cms page (and static block) _afterLoad() as can be seen in the Resource model (app/code/core/Mage/Cms/Page/Model/Resource/Page.php::_afterLoad). The distinction in this case is important to be forward compatible. If in the future the Page model grows a relation with customer groups, you'd have to update your code, while this would be implemented the same way in _afterLoad. Apr 14, 2015 at 14:14
1

Your solution did not work for me when I used store filters. The problem seems to be that getAllIds() does not take the store filter into account.

I found a different solution that does not require to load the pages in a loop:

foreach ($pages as $page) {
    /* @var $page Mage_Cms_Model_Page */
    $page->afterLoad();

    // apply changes here

    $page->save();
}

afterLoad() triggers _afterLoad() of the resource model which looks up the associated stores and sets them as store_id. Note that this is an array!

Now you can change the associated stores or leave them as they are, then the associations will stay the same on save.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.