IndexController.php #1

  • //
  • guest/
  • perforce_software/
  • chronicle/
  • main/
  • sites/
  • all/
  • modules/
  • wpimport/
  • controllers/
  • IndexController.php
  • View
  • Commits
  • Open Download .zip Download (19 KB)
<?php
/**
 * Handles importing a WordPress xml document into Chronicle; brings in users, content; if comment module
 * is enabled, brings in comments as well.
 *
 * @copyright   2011 Perforce Software. All rights reserved.
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
 * @version     <release>/<patch>
 */
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.<br/>'
                          . 'Please inform the users of their new passwords.<ul class="imported-users">';
                foreach ($authors as $id => $password) {
                    $message .= "<li><span class='username'>$id</span><span class='password'>$password</span></li>";
                }
                $message .= '</ul>';
            }

            $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();
    }
}
# Change User Description Committed
#1 16170 perforce_software Move Chronicle files to follow new path scheme for branching.
//guest/perforce_software/chronicle/sites/all/modules/wpimport/controllers/IndexController.php
#1 8972 Matt Attaway Initial add of the Chronicle source code