/ */ class Wpimport_IndexController extends Zend_Controller_Action { const BATCH_SIZE = 100; const STATUS_FILE = 'wpimport.status.file'; public $contexts = array( 'status' => array('json') ); protected $_feedLink = ''; protected $_totalFeedItems = 0; protected $_currentItemCount = 0; /** * Show form and handle import on submit. * Sets up site information, categories, and users before iterating over exported items. Items can be attachments * (files or images), menu items, or content entries. Tags are imported as categories and are not maintained as * separate data. Menu items may reference content entries or other menus not created until later in the feed * therefore the menu structure is created after entries are processed. */ public function indexAction() { $this->acl->check('content', 'manage'); $this->getHelper('layout')->setLayout('manage-layout'); $request = $this->getRequest(); $form = new Wpimport_Form_Configure; $view = $this->view; $view->form = $form; $view->headTitle()->set('WordPress Import'); if ($request->isPost() && $form->isValid($request->getPost()) && $form->importfile->receive()) { $xmlFile = $form->importfile->getFileName(); $module = P4Cms_Module::fetch('wpimport'); if (!Zend_Feed_Reader::isRegistered('WordPress')) { $basePath = $module->getPath(); Zend_Feed_Reader::addPrefixPath( 'Wp_FeedReader_Extension', $basePath ); Zend_Feed_Reader::registerExtension('WordPress'); } $feed = Zend_Feed_Reader::importFile($xmlFile); // write initial status update and disconnect browser $view->showStatus = getmypid(); $this->getHelper('layout')->direct()->content = $this->view->render('index/index.phtml'); echo $this->getHelper('layout')->render(); // disconnect the browser and continue $this->getHelper('browserDisconnect')->disconnect(); // calculate total count of items in the feed; pad it by 1% so when we do the final batch submit we don't // get odd status bar behavior in large data sets $this->_totalFeedItems = $feed->count() + count($feed->get('categories')) + count($feed->getWpAuthors()); $this->_totalFeedItems = $this->_totalFeedItems + round($this->_totalFeedItems / 100); $this->_updateStatus(); $this->_feedLink = $feed->getLink(); $this->_updateSite($feed); $this->_importCategories($feed); $authors = $this->_createUsers($feed); // recreate the primary menu; it will be populated as part of the import process if (P4Cms_Menu::exists('primary')) { P4Cms_Menu::fetch('primary')->delete(); } $primaryMenu = P4Cms_Menu::create( array( 'id' => 'primary', 'label' => 'Primary' ) )->save(); // store menu definitions in this array until all feeds are imported $menuItems = array(); $adapter = P4Cms_Content::getDefaultAdapter(); $batchEntryCount = 0; $batchCounter = 0; // iterate over the feed entries foreach ($feed as $feedEntry) { $this->_currentItemCount++; $this->_updateStatus(); if (!$adapter->inBatch()) { $batchCounter++; $adapter->beginBatch( 'Importing ' . $feed->count() . ' content entries. ' . 'Batch ' . $batchCounter . ' of ' . ceil($this->_totalFeedItems / static::BATCH_SIZE) . '.' ); } $postType = $feedEntry->get('wp:post_type'); switch ($postType) { case 'nav_menu_item': // do not import menu items with the 'draft' status as Chronicle does not support this concept. if ($feedEntry->get('wp:status') !== 'draft') { $itemDefinition = $this->_defineMenuItem($feedEntry); if (!array_key_exists($itemDefinition['parentId'], $menuItems)) { $menuItems[$itemDefinition['parentId']] = array('pages' => array()); } $menuItems[$itemDefinition['parentId']]['pages'][$itemDefinition['id']] = $itemDefinition; } break; case 'attachment': $contentEntry = $this->_getContentAssetEntry($feedEntry)->save(); break; case 'post': case 'page': default: $this->_getContentPageEntry($feedEntry)->save(); } $batchEntryCount++; // end and start a new batch if current one is full if ($batchEntryCount == static::BATCH_SIZE) { $batchEntryCount = 0; // this can take some time with no progress bar update; inform the user $this->_updateStatus(); $adapter->commitBatch(); } } // Create menu items from item definition list; this is delayed until all content is created so that // content references do attempt to reference non-exi $this->_createMenuItems($menuItems, $adapter); // commit or revert open batch if ($adapter->inBatch()) { $this->_updateStatus(); $adapter->commitBatch(); } $message = 'Import complete.'; if (count($authors)) { $message .= 'Users have been imported and their passwords reset.
' . 'Please inform the users of their new passwords.'; } $this->_writeStatusFile( array( 'time' => time(), 'done' => true, 'count' => $this->_totalFeedItems, 'total' => $this->_totalFeedItems, 'message' => $message ) ); // clear the global 'sites' cache. P4Cms_Cache::remove(P4Cms_Site::CACHE_KEY, 'global'); } else { $this->getHelper('helpUrl')->setUrl('installation.management.wordpress-import.html'); } } /** * Provide a status update in Json format. */ public function statusAction() { // enforce permissions. $this->acl->check('content', 'manage'); // set context $this->contextSwitch->initContext('json'); $processId = $this->getRequest()->getParam('processId'); $statusFile = sys_get_temp_dir() . '/' . static::STATUS_FILE . $processId; if (!file_exists($statusFile) ) { $status = array( 'label' => 'no status', 'message' => 'No current status file.', 'done' => true ); $this->view->status = $status; return; } $this->view->status = $this->_readStatusFile($processId); } /** * Read JSON-encoded information from temporary status file * * @param string $processId The process for which to read the status. * @return mixed The JSON-decoded contents from the status file. */ private function _readStatusFile($processId) { $statusFile = sys_get_temp_dir() . '/' . static::STATUS_FILE . $processId; $status = ''; if (file_exists($statusFile)) { $content = file_get_contents($statusFile); $status = Zend_Json::decode($content); } return $status; } /** * Write status information to the status file. * * @param array $data The status data to report. * @param string $processId The process for which to write the status. If empty, use current process. */ private function _writeStatusFile($data, $processId = null) { $processId = ($processId) ?: getmypid(); $statusFile = sys_get_temp_dir() . '/' . static::STATUS_FILE . $processId; file_put_contents($statusFile, Zend_Json::encode($data)); } /** * Write out current values to the status file. */ protected function _updateStatus() { $this->_writeStatusFile( array( 'time' => time(), 'done' => ($this->_currentItemCount == $this->_totalFeedItems), 'count' => $this->_currentItemCount, 'total' => $this->_totalFeedItems, 'message' => "Importing content. Item " . $this->_currentItemCount . " of " . $this->_totalFeedItems . "." ) ); } /** * Updates settings on the current site from the information provided in the feed. * * @param Zend_Feed_Reader_FeedInterface $feed The feed from which to update the site. */ protected function _updateSite(Zend_Feed_Reader_FeedInterface $feed) { // update branch url $branch = P4Cms_Site::fetchActive(); $branch->getConfig() ->setUrls(array($this->_feedLink)) ->save(); // update stream related to the branch $stream = $branch->getStream(); $stream->setName($feed->getTitle()) ->setDescription($feed->getDescription()) ->save(); } /** * Imports categories from the provided feed into Chronicle. * * @param Zend_Feed_Reader_FeedInterface $feed The feed from which to import the category structure. */ protected function _importCategories(Zend_Feed_Reader_FeedInterface $feed) { $categories = $feed->get('categories'); foreach ($categories as $categoryValues) { Category_Model_Category::store($categoryValues); } $this->_currentItemCount += count($categories); $this->_updateStatus(); } /** * Creates users from xml feed; because passwords aren't imported, creates them using uniqid and * returns them for later display. * * @param Zend_Feed_Reader_FeedInterface $feed The feed from which to import the users. * @return array List of newly-defined user ids and passwords. */ protected function _createUsers(Zend_Feed_Reader_FeedInterface $feed) { $created = array(); $wpAuthors = $feed->getWpAuthors(); foreach ($wpAuthors as $author) { $id = $author['id']; // catch any user creation failures and display a message try { // attempt to update existing user, fallback to creating a new user try { P4Cms_User::fetch($id)->setValues($author)->save(); } catch (P4Cms_Model_NotFoundException $e) { $author['password'] = P4Cms_User::generatePassword(10, 3); $user = new P4Cms_User; $user->setValues($author) ->save(); // default to member role P4Cms_Acl_Role::setUserRoles($user, array(P4Cms_Acl_Role::ROLE_MEMBER)); $created[$id] = $author['password']; } } catch (Exception $e) { $created[$id] = "Unable to create user; please create manually."; P4Cms_Log::log(print_r($e, true), P4Cms_Log::ERR); } } $this->_currentItemCount += count($wpAuthors); $this->_updateStatus(); return $created; } /** * Transforms a feed entry to an array containing the properties of a menu or menu item. * * @param Zend_Feed_Reader_EntryAbstract $feedEntry Entry to transform into a menu definition. * @return array Defined menu item. */ protected function _defineMenuItem(Zend_Feed_Reader_EntryAbstract $feedEntry) { $meta = $feedEntry->getPostMeta(); $targetId = (array_key_exists('menu_item_parent', $meta) && is_numeric($meta['menu_item_parent']) && (int)$meta['menu_item_parent'] > '0') ? $meta['menu_item_parent'] : 'primary'; $itemValues = array( 'id' => $feedEntry->get('wp:post_id'), 'order' => $feedEntry->get('wp:menu_order'), 'type' => 'P4Cms_Navigation_Page_Content', 'autoLabel' => true, 'contentId' => $meta['object_id'], 'parentId' => $targetId, 'pages' => array() ); return $itemValues; } /** * Converts a feed entry to a content entry with content type in the Pages category. Currently only Blog Post and * Basic Page types are supported. * * @param Zend_Feed_Reader_EntryAbstract $feedEntry The RSS feed entry. * @return P4Cms_Content The created content entry. */ protected function _getContentPageEntry(Zend_Feed_Reader_EntryAbstract $feedEntry) { $entryData = array( 'id' => $feedEntry->get('wp:post_id'), 'title' => $feedEntry->getTitle() ); $entryData['body'] = $feedEntry->getWpContent($this->_feedLink); if ($feedEntry->get('wp:status') == 'publish') { $entryData['workflow'] = array( 'state' => 'published', 'scheduled' => 'false' ); } $postType = $feedEntry->get('wp:post_type'); if ($postType == 'post') { $type = P4Cms_Content_Type::fetch('blog-post'); $entryData['date'] = $feedEntry->get('postDateGmt'); $entryData['category'] = array('categories' => $feedEntry->get('categories')); $entryData['excerpt'] = $feedEntry->getDescription(); $entryData['author'] = $feedEntry->get('dc:creator'); } else { $type = P4Cms_Content_Type::fetch('basic-page'); } $entryData['url'] = array( 'path' => strtolower($feedEntry->get('wp:post_name')), 'auto' => false ); $entry = new P4Cms_Content(); $entry->setContentType($type); $entry->setValues($entryData); return $entry; } /** * Converts a feed entry to a content entry with content type in the Assets category. * * @param Zend_Feed_Reader_EntryAbstract $feedEntry The RSS feed entry. * @return P4Cms_Content The created content entry. */ protected function _getContentAssetEntry(Zend_Feed_Reader_EntryAbstract $feedEntry) { $entryData = array( 'id' => $feedEntry->get('wp:post_id'), 'title' => $feedEntry->getTitle(), 'description' => $feedEntry->getDescription(), 'date' => $feedEntry->get('postDateGmt') ); $url = $feedEntry->get('wp:attachment_url'); $client = new Zend_Http_Client(); $client->setUri($url); try { $response = $client->request(); $validResponse = $response->isSuccessful(); } catch (Zend_Http_Client_Exception $e) { $validResponse = false; } $entry = new P4Cms_Content(); // verify remote content exists if ($validResponse) { $contentType = $response->getHeader('Content-Type'); list($type, $subtype) = explode('/', $contentType); if ($type == 'image') { $type = P4Cms_Content_Type::fetch('image'); $entryData['creator'] = $feedEntry->get('dc:creator'); $entryData['alt'] = $feedEntry->get('wp:post_name'); // update the id to be the image file name for images, for easier linking from the content $entryData['id'] = basename($url); } else { $type = P4Cms_Content_Type::fetch('file'); } $entry->setValue('file', $response->getBody()); $entry->setFieldMetadata( 'file', array( 'mimeType' => $response->getHeader('Content-Type'), 'filename' => basename($url), 'fileSize' => $response->getHeader('Content-Length') ) ); } else { // unable to fetch, make an image entry with no file $type = P4Cms_Content_Type::fetch('image'); } return $entry->setContentType($type)->setValues($entryData); } /** * Iterate over the list of provided menu item values, creating menu items. * * @param array $menuItems An array of values used to define menu items. * @param P4Cms_Record_Adapter $adapter The record adapter to use to save the menus. */ protected function _createMenuItems($menuItems, P4Cms_Record_Adapter $adapter) { // move child structures under the primary menu foreach ($menuItems as $id => $items) { if ($id == 'primary') { continue; } if (array_key_exists($id, $menuItems['primary']['pages'])) { $menuItems['primary']['pages'][$id]['pages'] = $menuItems['primary']['pages'][$id]['pages'] + $items['pages']; unset($menuItems[$id]); } } $menu = P4Cms_Menu::fetch('primary', null, $adapter); // add each entry to the menu. // if entry is an array, it must be a menu item or sub-menu. // otherwise, assume it's a menu property (e.g. label, order). foreach ($menuItems['primary']['pages'] as $entryId => $entry) { if (is_array($entry)) { $menu->addDefaultEntry($entry, $entryId); } else { $menu->setValue($entryId, $entry); } } $menu->save(); } }