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();
}