Perforce Chronicle 2012.2/486814
API Documentation

Content_IndexController Class Reference

Displays and manages content. More...

List of all members.

Public Member Functions

 addAction ()
 Add content.
 browseAction ()
 Handle requests to display a list of content Prepares list options form for tradtional requests.
 chooseTypeAction ()
 Choose a content type to add.
 deleteAction ()
 Delete content.
 downloadAction (P4Cms_Content $entry=null, $download=true)
 Download content.
 editAction (P4Cms_Content $entry=null, $skipAclCheck=null)
 Edit content.
 formAction ($getForm=false)
 Renders the content form for the requested entry or content type.
 imageAction ()
 Download the first image from a piece of content.
 indexAction ()
 Display a list of recent content.
 init ()
 Initialize object.
 manageAction ()
 List content for management.
 openedAction ()
 This action exposes the list of users currently editing a given content entry (along with the entry's status and change details).
 rollbackAction ()
 Rollback content.
 subFormAction ()
 Renders a specific sub-form of the content form.
 validateFieldAction ()
 Validate the passed field.
 viewAction (P4Cms_Content $entry=null, $skipAclCheck=null)
 Display content.

Public Attributes

 $contexts
const MAX_SCALE = 4000

Protected Member Functions

 _adjustImage ($imageData)
 Adjusts given image (represented by $imageData) according to the request params.
 _getContentForm (P4Cms_Content $entry)
 Creates the content form, passing in the formIdPrefix if present.
 _getEntryLayoutScript (P4Cms_Content $entry)
 Get the layout script to use for the given content entry.
 _getEntryViewScript (P4Cms_Content $entry)
 Get the view script to use for the given content entry.

Detailed Description

Displays and manages content.

There are a number of 'tags' that will be used to clear cache entries when content is modified. You can tag your cache entries with: p4cms_content - present on many entries; should only be cleared rarely p4cms_content_type - also present on many entries; cleared on a reset of types p4cms_content_<binhex(id)> - cleared when the given entry is edited/deleted p4cms_content_type_<binhex(id)> - cleared when the given type is edited/deleted p4cms_content_list - cleared when any content is added/edited/deleted intended for updating aggregate lists of content The <> brackets are just to show position, only the binhex'd id will be present.

Copyright:
2011-2012 Perforce Software. All rights reserved
License:
Please see LICENSE.txt in top-level folder of this distribution.
Version:
2012.2/486814

Member Function Documentation

Content_IndexController::_adjustImage ( imageData) [protected]

Adjusts given image (represented by $imageData) according to the request params.

Following request parameters are recognized: 'width' - target width in pixels; if not set, but 'height' is set, then target width will be computed from 'height' such that image keeps same aspect ratio as original 'height' - target height in pixels; if not set, but 'width' is set, then target height will be computed from 'width' such that image keeps same aspect ratio as original 'maxWidth' - maximum target width, image will be proportionally shrunk if computed width is greater than this value 'maxHeight' - maximum target height, image will be proportionally shrunk if computed height is greater than this value 'sharpen' - if set, image will be sharpened (applied after resizing)

Parameters:
string$imageDatainput image data
Returns:
string adjusted image data
    {
        $request    = $this->getRequest();
        $width      = (int) $request->getParam('width');
        $height     = (int) $request->getParam('height');
        $maxWidth   = (int) $request->getParam('maxWidth');
        $maxHeight  = (int) $request->getParam('maxHeight');
        $sharpen    = $request->getParam('sharpen');

        // early exit if nothing to apply
        if (!$width && !$height && !$maxWidth && !$maxHeight && !$sharpen) {
            return $imageData;
        }

        $image = new P4Cms_Image;
        $image->setData($imageData);

        $dimensions = $image->getDriver()->getImageSize();
        $ratio      = $dimensions['width'] / $dimensions['height'];

        // set target width and height:
        //  - set to original size if neither dimension was specified
        //  - if only one dimension was specified, compute the other one
        //    to keep the aspect ration of the original image
        if (!$width && !$height) {
            $width  = $dimensions['width'];
            $height = $dimensions['height'];
        } else if (!$width) {
            $width  = round($height * $ratio);
        } else if (!$height) {
            $height = round($width / $ratio);
        }

        // lower image dimensions if they exceed maximum dimensions (if provided)
        if ($maxHeight && $maxHeight < $height) {
            $width  = round($width * $maxHeight / $height);
            $height = $maxHeight;
        }
        if ($maxWidth && $maxWidth < $width) {
            $height = round($height * $maxWidth / $width);
            $width  = $maxWidth;
        }

        // resize image according to computed width and height (if they differ from original size)
        if ($width !== $dimensions['width'] || $height !== $dimensions['height']) {
            if ($width > static::MAX_SCALE || $height > static::MAX_SCALE) {
                throw new Content_Exception(
                    "Width or height exceeds maximum scale of " . static::MAX_SCALE . "px."
                );
            }

            $image->transform('scale', array($width, $height));
        }

        // sharpen output image if requested
        if ($sharpen) {
            $image->transform('sharpen');
        }

        return $image->getData();
    }
Content_IndexController::_getContentForm ( P4Cms_Content entry) [protected]

Creates the content form, passing in the formIdPrefix if present.

Parameters:
P4Cms_Content$entryThe content entry to make a form for.
Returns:
Content_Form_Content The completed form.
    {
        $options = array('entry' => $entry);

        // if request specifies an id prefix, add it to options
        $request = $this->getRequest();
        $options['idPrefix'] = $request->getParam('formIdPrefix');

        return new Content_Form_Content($options);
    }
Content_IndexController::_getEntryLayoutScript ( P4Cms_Content entry) [protected]

Get the layout script to use for the given content entry.

If the content type specifies a valid layout, returns it. Otherwise, returns the current default layout.

Parameters:
P4Cms_Content$entrythe entry to be rendered.
Returns:
string the name of the layout script to use.
    {
        $layout = $entry->getContentType()->getLayout();
        $suffix = '.' . $this->_helper->viewRenderer->getViewSuffix();
        if ($layout && $this->view->getScriptPath($layout . $suffix)) {
            return $layout;
        }

        return $this->_helper->layout->getLayout();
    }
Content_IndexController::_getEntryViewScript ( P4Cms_Content entry) [protected]

Get the view script to use for the given content entry.

Searches view script paths for most specific template. 1. index/view-entry-<id>.phtml 2. index/view-type-<id>.phtml 3. index/view.phtml

Parameters:
P4Cms_Content$entrythe entry to be rendered.
Returns:
string the name of the view script to use.

p4cms.content.view.scripts Return the passed scripts array, making any modifications (add, remove or change values), to influence the view script that ends up being selected for rendering. The first view script filename in the list that exists in the view's search paths gets used. Note that the filename should not include the suffix (typically ".phtml"). array $scripts The list of view script filenames. P4Cms_Content $entry The content entry to render.

    {
        $scripts = array();

        // convention for entry ids.
        if ($entry->getId()) {
            $scripts[] = 'index/view-entry-'. $entry->getId();
        }

        // convention for content types.
        $scripts[] = 'index/view-type-'. $entry->getContentType()->getId();

        // let third-parties add to or alter the view script conventions
        $scripts = P4Cms_PubSub::filter(
            'p4cms.content.view.scripts',
            $scripts,
            $entry
        );

        // find the first template that exists among the possible scripts.
        $suffix = '.' . $this->_helper->viewRenderer->getViewSuffix();
        foreach ($scripts as $script) {
            if ($this->view->getScriptPath($script . $suffix)) {
                return basename($script);
            }
        }

        // no match, fallback to default view.
        return 'view';
    }
Content_IndexController::addAction ( )

Add content.

If not type has been selected, forwards to choose-type action. If a type is indicated, create new entry and forward to edit action.

    {
        // enforce permissions.
        $this->acl->check('content', 'add');

        $request = $this->getRequest();

        // if get request and no valid type specified, prompt user for type.
        // check 'contentType' param first, and 'type' param second.
        $type = $request->getParam('contentType', $request->getParam('type'));
        if ($request->isGet() && (!$type || !P4Cms_Content_Type::exists($type))) {
            return $this->_forward('choose-type');
        }

        // get the content type definition.
        $type = P4Cms_Content_Type::fetch($type);

        // create new content model and set type.
        $entry = new P4Cms_Content;
        $entry->setContentType($type);

        // set the page title.
        $this->view->headTitle()->set("Add '" . $type->getLabel() . "'");

        return $this->editAction($entry, 'edit');
    }
Content_IndexController::browseAction ( )

Handle requests to display a list of content Prepares list options form for tradtional requests.

Prepares content query for context specific requests.

p4cms.content.grid.actions Modify the passed menu (add/modify/delete items) to influence the actions shown on entries in the Manage Content grid. P4Cms_Navigation $actions A menu to hold grid actions.

p4cms.content.grid.data.item Return the passed item after applying any modifications (add properties, change values, etc.) to influence the row values sent to the Manage Content grid. array $item The item to potentially modify. mixed $model The original object/array that was used to make the item. Ui_View_Helper_DataGrid $helper The view helper that broadcast this topic.

p4cms.content.grid.data Adjust the passed data (add properties, modify values, etc.) to influence the row values sent to the Manage Content grid. Zend_Dojo_Data $data The data to be filtered. Ui_View_Helper_DataGrid $helper The view helper that broadcast this topic.

p4cms.content.grid.populate Adjust the passed query (possibly based on values in the passed form) to filter which content entries will be shown on the Manage Content grid. P4Cms_Record_Query $query The query to filter content entries. P4Cms_Form_PubSubForm $form A form containing filter options.

p4cms.content.grid.render Make adjustments to the datagrid helper's options pre-render (e.g. change options to add columns) for the Manage Content grid. Ui_View_Helper_DataGrid $helper The view helper that broadcast this topic.

p4cms.content.grid.form Make arbitrary modifications to the Manage Content filters form. P4Cms_Form_PubSubForm $form The form that published this event.

p4cms.content.grid.form.subForms Return a Form (or array of Forms) to have them added to the Manage Content filters form. The returned form(s) should have a 'name' set on them to allow them to be uniquely identified. P4Cms_Form_PubSubForm $form The form that published this event.

p4cms.content.grid.form.preValidate Allows subscribers to adjust the Manage Content filters form prior to validation of the passed data. For example, modify element values based on related selections to permit proper validation. P4Cms_Form_PubSubForm $form The form that published this event. array $values An associative array of form values.

p4cms.content.grid.form.validate Return false to indicate the Manage Content filters form is invalid. Return true to indicate your custom checks were satisfied, so form validity should be unchanged. P4Cms_Form_PubSubForm $form The form that published this event. array $values An associative array of form values.

p4cms.content.grid.form.populate Allows subscribers to adjust the Manage Content filters form after it has been populated with the passed data. P4Cms_Form_PubSubForm $form The form that published this event. array $values The values passed to the populate method.

    {
        // ensure users are allowed to access content before the list is displayed
        $this->acl->check('content', 'access');

        // get list option sub-forms.
        $request        = $this->getRequest();
        $gridNamespace  = 'p4cms.content.grid';
        $form           = new Ui_Form_GridOptions(
            array(
                'namespace'   => $gridNamespace
            )
        );
        $form->populate($request->getParams());

        // the request can specify which columns appear - only permit column names.
        $columns = $request->getParam('columns', array('type', 'title', 'modified'));
        $columns = array_filter($columns, 'is_string');

        // if the title column is requested, ensure that it isn't "linkified"
        $columns = array_flip($columns);
        if (isset($columns['title'])) {
            $columns['title'] = array(
                'formatter' => 'p4cms.content.grid.Formatters.titleNoLink'
            );
        }

        // setup access-restricted view, manageAction will expand these settings as needed
        $view                       = $this->view;
        $view->form                 = $form;
        $view->pageSize             = $request->getParam('count', 100);
        $view->rowOffset            = $request->getParam('start', 0);
        $view->pageOffset           = round($view->rowOffset / $view->pageSize, 0) + 1;
        $view->columns              = $columns;
        $view->showAddLink          = false;
        $view->showDeleteButton     = false;
        $view->selectionMode        = $request->getParam('selectionMode', 'single');

        // set DataGrid view helper namespace
        $helper = $view->dataGrid();
        $helper->setNamespace($gridNamespace);

        // early exit for standard requests (ie. not json or partial)
        if (!$this->contextSwitch->getCurrentContext()) {
            $this->_helper->layout->setLayout('manage-layout');
            return;
        }

        // construct list query - allow third-parties to influence query.
        $query = new P4Cms_Record_Query;
        $query->setRecordClass('P4Cms_Content');
        try {
            $result = P4Cms_PubSub::publish($gridNamespace . '.populate', $query, $form);
        } catch (Exception $e) {
            P4Cms_Log::logException("Error building content list query.", $e);
        }

        // prepare sorting options
        $sortKey = $sortKeyOriginal = $request->getParam('sort', '-#REdate');
        $sortFlags = array();
        // handle sort order; descending sort identified with '-' prefix.
        if (substr($sortKey, 0, 1) == '-') {
            $sortKey = substr($sortKey, 1);
            $sortFlags[] = P4Cms_Record_Query::SORT_DESCENDING;
        }

        // if we're sorting via an internal attribute, use the traditional
        // syntax by knocking out the query options and reversing the
        // results if necessary.
        if (strpos($sortKey, '#') === 0) {
            $sortFlags = null;
            $query->setReverseOrder($sortKey === $sortKeyOriginal ? false : true);
        }

        // some column names differ from the model, so we need a map.
        $sortKeyMap = array(
            'type' => 'contentType'
        );

        // look up requested sort column in our map.
        $sortKey = isset($sortKeyMap[$sortKey])
            ? $sortKeyMap[$sortKey]
            : $sortKey;

        $query->setSortBy($sortKey, $sortFlags);

        // add query to the view.
        $view->query = $query;
    }
Content_IndexController::chooseTypeAction ( )

Choose a content type to add.

Prompts the user to select from the available content types.

    {
        // load types into view.
        $this->view->typeGroups = P4Cms_Content_Type::fetchGroups();
        $this->view->headTitle()->set('Choose Content Type');
    }
Content_IndexController::deleteAction ( )

Delete content.

Supports deleting of multiple content entries that will be deleted in a batch - i.e. either all of them or none.

List of entry ids to delete are passed in the 'ids' parameter, however the method also accepts passing entry id(s) via 'id' parameter (this will have precedence if both 'id and 'ids' parameters are present).

Requires HTTP post request to perform delete. Traditional requests are redirected to the index and a notification is set. Context specific requests are rendered using the appropriate context view script.

    {
        $request = $this->getRequest();

        // set up the view
        $form       = new Content_Form_Delete;
        $view       = $this->view;
        $view->form = $form;

        // populate the form from the request
        // support passing entry ids in both 'id' and/or 'ids' params,
        // ensure 'ids' param will get the value from 'id' if it was set
        $params        = $request->getParams();
        $params['ids'] = $request->getParam('id', $request->getParam('ids'));
        $form->populate($params);

        // if there are posted data, validate the form and delete selected entries
        if ($request->isPost()) {
            // if form is invalid, set response code and exit
            if (!$form->isValid($params)) {
                $this->getResponse()->setHttpResponseCode(400);
                $view->errors = $form->getMessages();
                return;
            }

            // get adapter for batch and fetch all entries to delete
            $adapter = P4Cms_Content::getDefaultAdapter();
            $entries = P4Cms_Content::fetchAll(array('ids' => (array) $form->getValue('ids')));

            // attempt to delete all specified entries in a batch
            $adapter->beginBatch($form->getValue('comment') ?: 'No description provided.');
            foreach ($entries as $entry) {
                try {
                    // enforce permissions
                    $this->acl->check('content/' . $entry->getId(), 'delete');

                    // delete content entry
                    $entry->delete();
                } catch (Exception $e) {
                    // cannot delete the entry; revert the batch, set the response code and exit
                    $adapter->revertBatch();
                    $this->getResponse()->setHttpResponseCode(400);
                    $view->message = $e->getMessage();
                    return;
                }
            }

            // commit batch
            $adapter->commitBatch();

            // clear any affected cached entries
            $tags       = array('p4cms_content_list');
            $deletedIds = $entries->invoke('getId');
            foreach ($deletedIds as $entryId) {
                $tags[] = 'p4cms_content_' . bin2hex($entryId);
            }
            P4Cms_Cache::clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, $tags);

            // add notification and redirect to index for traditional requests
            if (!$this->contextSwitch->getCurrentContext()) {
                $message = 'Deleted ' . count($deletedIds)
                    . (count($deletedIds) === 1 ? ' content entry.' : ' content entries.');
                P4Cms_Notifications::add($message, P4Cms_Notifications::SEVERITY_SUCCESS);
                return $this->redirector->gotoSimple('index');
            }

            // pass list of deleted entries to the view
            $view->ids = $deletedIds;
        }
    }
Content_IndexController::downloadAction ( P4Cms_Content entry = null,
download = true 
)

Download content.

Serves the contents of the requested field or if no field is specified, uses the file field. If the requested content entry does not exist or has no suitable field, forwards to page-not-found.

Parameters:
P4Cms_Content$entryoptional - entry instance to download
boolean$downloadoptional - indicate whether content should be downloaded defaults to true. influences content-disposition header.
    {
        // enforce permissions - deny if user doesn't have access permission.
        $this->acl->check('content', 'access');

        // get the content entry to download.
        $request = $this->getRequest();
        $id      = $request->getParam('id');

        // if a version is present, generate rev-spec.
        $revspec = $request->getParam('version');
        $options = null;
        if ($revspec) {
            // enforce permissions - viewing historic versions requires 'access-history' privilege.
            $this->acl->check('content', 'access-history');
            $revspec = '#' . $revspec;
            $options = array('includeDeleted' => true);
        }

        try {
            $entry = $entry ?: P4Cms_Content::fetch($id . $revspec, $options);
        } catch (Exception $e) {
            // we only have special handling for specific types; rethrow anything else
            if (!$e instanceof P4Cms_Record_NotFoundException
                && !$e instanceof InvalidArgumentException
            ) {
                throw $e;
            }

            return $this->_forward('page-not-found', 'index', 'error');
        }

        // tag the page cache so it can be appropriately cleared later
        if (P4Cms_Cache::canCache('page')) {
            P4Cms_Cache::getCache('page')
                ->addTag('p4cms_content_'      . bin2hex($entry->getId()))
                ->addTag('p4cms_content_type_' . bin2hex($entry->getContentTypeId()));
        }

        // determine what content field to deliver.
        // if the request specifies a field, serve it.
        // fallback to the entry's file content field.
        $field = $request->getParam('field', $entry->getFileContentField());

        // if there is still no field to serve, indicate 404.
        if (!$field) {
            $this->_forward('page-not-found', 'index', 'error');
            return;
        }

        // get entry data to download
        $data = $entry->getValue($field);

        // obtain file metadata.
        $metadata = $entry->getFieldMetadata($field);
        $filename = isset($metadata['filename']) ? $metadata['filename'] : null;
        $mimeType = isset($metadata['mimeType']) ? $metadata['mimeType'] : 'application/octet-stream';

        // if data represents an image, adjust it before sending to output
        if (strpos($mimeType, 'image/') === 0) {
            try {
                $data = $this->_adjustImage($data);
            } catch (Exception $e) {
                P4Cms_Log::log("Image adjust failed: " . $e->getMessage(), P4Cms_Log::WARN);
            }
        }

        $this->getResponse()->setHeader('Content-Type', $mimeType);
        if ($download) {
            $this->getResponse()->setHeader(
                'Content-Disposition',
                'attachment;' . ($filename ? ' filename="' . $filename . '"' : '')
            );
        }

        // if entry's field value is empty, render the page
        if (!$data) {
            return $this->viewAction($entry);
        }

        // disable autorendering for the download
        $this->_helper->viewRenderer->setNoRender();
        $this->_helper->layout->disableLayout();

        print $data;
    }
Content_IndexController::editAction ( P4Cms_Content entry = null,
skipAclCheck = null 
)

Edit content.

HTTP get requests are forwarded to the view action for in-place editing. Post requests are validated and saved.

The p4cms.content.form events documented on the manage action will also be broadcast when this action is accessed.

Parameters:
P4Cms_Content$entryoptional - content entry to display.
null | string | array$skipAclCheckoptional - pass string or array of strings enumerating the privilegs that can be skipped
    {
        $request     = $this->getRequest();
        $headVersion = $request->getParam('headVersion');
        $entryId     = $request->getParam('id');

        // If we have a head version and ID ensure we fetch that
        // revision of the entry. This will cause a conflict
        // exception on save should we be out of date.
        if ($entryId && $headVersion) {
            $entryId = $entryId . "#" . $headVersion;
        }

        // enforce permissions.
        if (!in_array('edit', (array)$skipAclCheck)) {
            $this->acl->check('content/' . $entryId, 'edit');
        }

        // forward get requests to view action
        if ($request->isGet()) {

            // inject javascript to enable the appropariate add/edit toolbar button
            $action = $entryId ? 'startEdit' : 'startAdd';
            $this->view->dojo()->addOnLoad(
                "function(){ p4cms.content.$action(); }"
            );

            return $this->viewAction($entry, $skipAclCheck);
        }

        // if not called with an entry, we must fetch one.
        try {
            $entry = $entry ?: P4Cms_Content::fetch($entryId, array('includeDeleted' => true));
        } catch (P4Cms_Record_NotFoundException $e) {
            return $this->_forward('page-not-found', 'index', 'error');
        }

        // construct and populate the content form.
        $form = $this->_getContentForm($entry);
        $form->populate($request->getPost());

        // populate the view.
        $this->view->entry   = $entry;
        $this->view->type    = $entry->getContentType();
        $this->view->form    = $form;
        $this->view->isValid = true;

        // if form was posted and is valid, save it.
        if ($request->isPost() && $form->isValid($request->getPost())) {
            try {
                // if this content entry doesn't have an owner,
                // set the content owner to the current user.
                $entry->setOwner($entry->getOwner() ?: P4Cms_User::fetchActive());

                // if a comment was provided by the user, set it as change
                // description, otherwise provide default value
                $saveForm    = $form->getSubForm('save');
                $description = $saveForm && $saveForm->getValue('comment')
                    ? $saveForm->getValue('comment')
                    : 'Saved content change.';

                // copy form values to the content entry and save it
                // entry is a pub/sub record so third-parties can participate
                // ensure we throw on conflict so we can alert user
                $entry->setValues($form)
                      ->save($description, P4Cms_Content::SAVE_THROW_CONFLICT);

                // clear any cached entries related to this page
                // @todo connect to p4cms.content.record.postSave
                P4Cms_Cache::clean(
                    Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG,
                    array('p4cms_content_' . bin2hex($entry->getId()), 'p4cms_content_list')
                );


                // remove ourselves from the 'opened' list if we had an identified record
                if ($entryId) {
                    $user   = P4Cms_User::fetchActive();
                    $record = new P4Cms_Content_Opened;
                    $record->setAdapter($entry->getAdapter())
                           ->setId($entry->getId())
                           ->setUserStartTime($user, null)
                           ->setUserPingTime($user,  null)
                           ->setUserEditTime($user,  null)
                           ->save();
                }

                // notify success and redirect for traditional requests.
                if (!$this->contextSwitch->getCurrentContext()) {
                    P4Cms_Notifications::add(
                        'Content saved.',
                        P4Cms_Notifications::SEVERITY_SUCCESS
                    );

                    return $this->_redirect($request->getBaseUrl());
                }
            } catch (P4_Connection_ConflictException $e) {
                // if we received a conflict exception we need to mark
                // the form as being in error and inform the view.
                $form->markAsError();
                $this->view->isConflict = true;
            }
        }

        // save failed validation - include errors in response
        if ($form->isErrors()) {
            $this->view->isValid     = false;
            $this->view->form        = $form;
            $this->view->errors      = array(
                'form'      => $form->getErrorMessages(),
                'elements'  => $form->getMessages()
            );
        }
    }
Content_IndexController::formAction ( getForm = false)

Renders the content form for the requested entry or content type.

Parameters:
boolean$getFormif true, return the content form.

The p4cms.content.form events documented on the manage action will also be broadcast when this action is accessed.

    {
        $request = $this->getRequest();
        $type    = $request->getParam('contentType');
        $id      = $request->getParam('id');

        // tag the page cache so it can be appropriately cleared later
        if (P4Cms_Cache::canCache('page')) {
            P4Cms_Cache::getCache('page')
                ->addTag('p4cms_content_'      . bin2hex($id))
                ->addTag('p4cms_content_type_' . bin2hex($type));
        }

        // if a version is present, generate rev-spec.
        $revspec = $request->getParam('version');
        if ($revspec) {
            // enforce permissions - viewing historic versions requires 'access-history' privilege.
            $this->acl->check('content', 'access-history');

            $revspec = '#' . $revspec;
        }

        $entry = $id
            ? P4Cms_Content::fetch($id . $revspec, $revspec ? array('includeDeleted' => true) : null)
            : new P4Cms_Content(array(P4Cms_Content::TYPE_FIELD => $type));

        if (!$getForm && $request->isPost()) {
            return $this->editAction($entry, 'edit');
        }

        // construct and populate the content form.
        $form = $this->_getContentForm($entry);
        $form->populate($request->getPost());

        // populate the view.
        $this->view->entry = $entry;
        $this->view->type  = $type;
        $this->view->form  = $form;

        // explicitly set partial context for other requests.
        $this->contextSwitch->initContext('partial');

        return $form;
    }
Content_IndexController::imageAction ( )

Download the first image from a piece of content.

Looks at the type definition for the first 'imagefile' field of the requested content entry. If it has a valid content serves it.

If content type has no imagefile field, serves the content of the first available 'file' field if it contains a valid image.

Otherwise, forward to page-not-found.

    {
        // enforce permissions - deny if user doesn't have access permission.
        $this->acl->check('content', 'access');

        // get the content entry to download.
        $request = $this->getRequest();

        // if a version is present, generate rev-spec.
        $revspec = $request->getParam('version');
        $options = null;
        if ($revspec) {
            // enforce permissions - viewing historic versions requires 'access-history' privilege.
            $this->acl->check('content', 'access-history');
            $revspec = '#' . $revspec;
            $options = array('includeDeleted' => true);
        }

        // try to retreive the requested record and type; error out if not found
        try {
            $entry = P4Cms_Content::fetch($request->getParam('id') . $revspec, $options);
        } catch (Exception $e) {
            // we only have special handling for specific types; rethrow anything else
            if (!$e instanceof P4Cms_Record_NotFoundException
                && !$e instanceof InvalidArgumentException
            ) {
                throw $e;
            }

            return $this->_forward('page-not-found', 'index', 'error');
        }

        // check to see if there is an image element on the content type
        $image    = null;
        $elements = $entry->getContentType()->getFormElements();
        foreach ($elements as $element) {
            if ($element instanceof P4Cms_Form_Element_ImageFile) {
                $image = $element->getName();
                break;
            }
        }

        // if there was no image, check the file, which may be an image
        if (!$image && $entry->hasFileContentField()) {
            $image = $entry->getFileContentField();
        }

        // if request specifies a field, use that, otherwise
        // use the field that we detected above
        $image = $request->getParam('field', $image);

        // if we've found something, ensure it's an image and has content,
        // assuming that an empty element won't have mime data.
        // we also allow pdf files to be 'viewed as images' - support for
        // rendering pdf documents directly in the browser is pretty common
        if ($image) {
            $metadata = $entry->getFieldMetadata($image);
            $mimeType = isset($metadata['mimeType'])
                ? $metadata['mimeType']
                : null;

            if (strpos($mimeType, 'image/') !== 0 && $mimeType !== 'application/pdf') {
                $image = null;
            }
        }

        // tag the page cache so it can be appropriately cleared later
        if (P4Cms_Cache::canCache('page')) {
            P4Cms_Cache::getCache('page')
                ->addTag('p4cms_content_'      . bin2hex($entry->getId()))
                ->addTag('p4cms_content_type_' . bin2hex($entry->getContentTypeId()));
        }

        // if we didn't find a valid image, send a 404
        if (!$image) {
            $this->_forward('page-not-found', 'index', 'error');
            return;
        }

        // if we did find a valid image, let the download action handle it
        $request->setParam('field', $image);
        $this->downloadAction($entry, false);
    }
Content_IndexController::indexAction ( )

Display a list of recent content.

    {
        $this->view->canAdd = $this->acl->isAllowed('content', 'add');
        if (!$this->acl->isAllowed('content', 'access')) {
            return;
        }

        $this->view->recent = P4Cms_Content::fetchAll(
            P4Cms_Record_Query::create()
            ->setMaxRows(5)
            ->setSortBy(P4Cms_Record_Query::SORT_DATE)
            ->setReverseOrder(true)
        );

        // tag the page cache so it can be appropriately cleared later
        if (P4Cms_Cache::canCache('page')) {
            P4Cms_Cache::getCache('page')->addTag('p4cms_content_list');
        }
    }
Content_IndexController::init ( )

Initialize object.

Extends parent to add content and content type tags to page cache if it is present.

    {
        parent::init();

        if (P4Cms_Cache::canCache('page')) {
            P4Cms_Cache::getCache('page')
                ->addTag('p4cms_content')
                ->addTag('p4cms_content_type');
        }
    }
Content_IndexController::manageAction ( )

List content for management.

To provide an action the grid participants subscribe to the topic: p4cms.content.grid.actions

One argument will be passed to subscribers: P4Cms_Navigation $actions a navigation container to hold all actions

They are expected to add/modify/etc. pages to the navigation container. The pages will be rendered to a Menu Dijit so utilizing the onClick and, optionally, onShow events is advised to control menu item behaviour.

The default actions are added during module init. To modify/remove a default action, subscribe during module load, or later, to ensure the default nav entries are already present.

    {
        // enforce permissions.
        $this->acl->check('content', 'manage');

        // generate page with data grid and form
        $this->browseAction();

        // the request can specify which columns appear - only permit column names.
        $request = $this->getRequest();
        $columns = $request->getParam('columns', array('type', 'title', 'modified', 'actions'));
        $columns = array_filter($columns, 'is_string');

        // enable manage-specific settings
        $view                       = $this->view;
        $view->showAddLink          = $this->acl->isAllowed('content', 'add');
        $view->showDeleteButton     = $this->acl->isAllowed('content', 'delete');
        $view->selectionMode        = 'extended';
        $view->columns              = $columns;

        $view->headTitle()->set('Manage Content');
        $this->getHelper('helpUrl')->setUrl('content.html');
    }
Content_IndexController::openedAction ( )

This action exposes the list of users currently editing a given content entry (along with the entry's status and change details).

If posted to with an event parameter set to start/ping/stop the active user will be updated in opened list.

If the event param is start, the active user will be added to the opened list with a pingTime of now and editTime of null.

If the event param is ping, the active user will have their ping time updated to now. The edit time will be unchanged (the edit time is updated whenever 'validateFieldAction' is called).

For the stop event the active user will be removed from the opened list.

Lastly, if the event param is missing or unrecognized the status will be returned but no changes will be made to the users opened details.

Even if the caller lacks 'access-history' permissions conflicts involving a deleted revision are reported.

    {
        // require edit access as that is the only time this action makes sense
        $this->acl->check('content', 'edit');

        // force json context; without fields the html version won't work well.
        $this->contextSwitch->initContext('json');

        // force the settings we care about to known values, we don't
        // want to risk leaking field values via this action.
        $request = $this->getRequest();
        $request->setParam('fields',        false)
                ->setParam('includeChange', true)
                ->setParam('includeStatus', true)
                ->setParam('includeOpened', true)
                ->setParam('version',       'head');

        // let view take care of fetching the entry and providing output
        $this->viewAction(null, array('access', 'access-history'));

        // exit if no content entry could be retrieved
        $entry = $this->view->entry;
        if (!$entry instanceof P4Cms_Content) {
            return;
        }

        // if its a post deal with the start/ping/stop events
        if ($request->isPost()) {
            $user   = P4Cms_User::fetchActive();
            $record = new P4Cms_Content_Opened;
            $record->setAdapter($entry->getAdapter())
                   ->setId($entry->getId());

            switch ($request->getParam('event')) {
                case 'start':
                    $record->setUserStartTime($user)
                           ->setUserPingTime($user)
                           ->setUserEditTime($user, null)
                           ->save();
                    break;
                case 'ping':
                    $record->setUserPingTime($user)
                           ->save();
                    break;
                case 'stop':
                    $record->setUserPingTime($user, null)
                           ->setUserEditTime($user, null)
                           ->save();
                    break;
            }
        }
    }
Content_IndexController::rollbackAction ( )

Rollback content.

If the record id and change are valid; rolls back the specified entry and redirects to view so the result will be shown.

    {
        $request = $this->getRequest();
        $id      = $request->getParam('id');

        // enforce permissions - requires both access-history and edit privileges.
        $message = 'You do not have permission to rollback this content entry.';
        $this->acl->check('content',        'access-history', null, $message);
        $this->acl->check('content/' . $id, 'edit',           null, $message);

        if ($request->getParam('change')) {
            $revSpec = '@' . $request->getParam('change');
        } else if ($request->getParam('version')) {
            $revSpec = '#' . $request->getParam('version');
        } else {
            throw new InvalidArgumentException(
                'A version or change number must be specified'
            );
        }

        // fetch the entry at the requested revision
        $entry = P4Cms_Content::fetch(
            $id . $revSpec,
            array('includeDeleted' => true)
        );

        $version = $entry->toP4File()->getStatus('headRev');

        $entry->save('Rollback to version '  . $version);

        // clear any cached entries related to this page
        P4Cms_Cache::clean(
            Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG,
            array('p4cms_content_' . bin2hex($entry->getId()), 'p4cms_content_list')
        );

        // redirect to view the specified entry
        $this->getHelper('redirector')->gotoUrl($entry->getUri());
    }
Content_IndexController::subFormAction ( )

Renders a specific sub-form of the content form.

Forces the 'partial' request context.

    {
        $form = $this->formAction(true);

        // ensure requested sub-form exists.
        $subForm = $form->getSubForm($this->getRequest()->getParam('form'));
        if (!$subForm instanceof Zend_Form) {
            throw new Content_Exception(
                "Cannot get sub-form. Requested sub-form does not exist."
            );
        }

        // populate the view.
        $this->view->subForm = $subForm;
    }
Content_IndexController::validateFieldAction ( )

Validate the passed field.

    {
        // force json context.
        $this->contextSwitch->initContext('json');

        // extract field/value from request.
        $request   = $this->getRequest();
        $field     = $request->getParam('field');
        $value     = $request->getParam('value');
        $contentId = $request->getParam('contentId', null);

        // enforce permissions.
        if ($contentId) {
            $this->acl->check('content' . '/' . $contentId, 'edit');
        } else {
            $this->acl->check('content', 'add');
        }

        // verify that field is specified
        if ($field == '') {
            throw new P4Cms_Content_Exception(
                "Cannot validate field - no field given."
            );
        }

        // fetch the type to ensure it is valid
        $type = P4Cms_Content_Type::fetch($request->getParam('contentType'));

        // setup an entry to get the display value from
        $entry = new P4Cms_Content;
        $entry->setContentType($type)
              ->setValue($field, $value);

        // get the content type and element definition.
        $type    = $entry->getContentType();
        $element = $type->getFormElement($field);

        // validate the field.
        $isValid = $element->isValid($value);

        // get the entry at the head revision
        $options    = array('includeDeleted' => true);
        $headEntry  = null;
        if ($contentId && P4Cms_Content::exists($contentId, $options)) {
            $headEntry = P4Cms_Content::fetch($contentId, $options);

            // update the opened info to reflect our edit/ping
            $user   = P4Cms_User::fetchActive();
            $record = new P4Cms_Content_Opened;
            $record->setAdapter($entry->getAdapter())
                   ->setId($contentId)
                   ->setUserPingTime($user)
                   ->setUserEditTime($user)
                   ->save();
        }

        // populate the view.
        $this->view->type           = $type;
        $this->view->entry          = $headEntry;
        $this->view->element        = $element;
        $this->view->fieldName      = $field;
        $this->view->fieldValue     = $value;
        $this->view->isValid        = $isValid;
        $this->view->errors         = $element->getMessages();
        $this->view->displayValue   = $entry->getDisplayValue($field);
    }
Content_IndexController::viewAction ( P4Cms_Content entry = null,
skipAclCheck = null 
)

Display content.

Parameters:
P4Cms_Content$entryoptional - content entry to display.
null | string | array$skipAclCheckoptional - pass string or array of strings enumerating the privilegs that can be skipped
    {
        // enforce permissions.
        if (!in_array('access', (array)$skipAclCheck)) {
            $this->acl->check('content', 'access');
        }

        // if not called with an entry, we must fetch one.
        $request = $this->getRequest();

        // if a version is present, generate rev-spec.
        $revspec = $request->getParam('version');
        if ($revspec) {
            // enforce permissions - viewing historic versions requires 'access-history' privilege.
            if (!in_array('access-history', (array)$skipAclCheck)) {
                $this->acl->check('content', 'access-history');
            }

            // inject javascript to enable the history toolbar button
            // if the user came directly to the view action.
            // the edit action forwards here and doesn't want this.
            if ($request->getActionName() == 'view') {
                $this->view->dojo()->addOnLoad(
                    "function(){ p4cms.content.startHistory(); }"
                );
            }

            $revspec = '#' . $revspec;
        }

        // attempt to fetch content entry.
        try {
            $entry = $entry ?: P4Cms_Content::fetch(
                $request->getParam('id') . $revspec,
                $revspec ? array('includeDeleted' => true) : null
            );
        } catch (Exception $e) {
            // we only have special handling for specific types; rethrow anything else
            if (!$e instanceof P4Cms_Record_NotFoundException
                && !$e instanceof InvalidArgumentException
            ) {
                throw $e;
            }

            return $this->_forward('page-not-found', 'index', 'error');
        }

        // validate the content type - if the type has no id, we assume it
        // is missing and was dynamically generated by the content class.
        $type   = $entry->getContentType();
        $typeId = $entry->getContentTypeId();
        if (!$type->getId()) {
            throw new Content_Exception(
                "This content entry requires a missing content type ('$typeId')."
            );
        }

        // populate the view.
        $view           = $this->view;
        $view->entry    = $entry;
        $view->type     = $type;


        // request can specify an array of fields, true or false for json context.
        $view->fields   = $request->getParam('fields', true);

        // request can request the change be included for json context.
        $view->includeChange = (bool) $request->getParam('includeChange', false);

        // request can request the status be included for json context.
        $view->includeStatus = (bool) $request->getParam('includeStatus', false);

        // request can request the opened status be included for json context.
        $view->includeOpened = (bool) $request->getParam('includeOpened', false);

        // tag the page cache so it can be appropriately cleared later
        if (P4Cms_Cache::canCache('page')) {
            P4Cms_Cache::getCache('page')
                ->addTag('p4cms_content_'      . bin2hex($entry->getId()))
                ->addTag('p4cms_content_type_' . bin2hex($entry->getContentTypeId()));
        }

        // set the page title if entry has one.
        if (strlen($entry->getTitle())) {
            $this->view->headTitle()->set($entry->getTitle());
        }

        // set the meta description from the entry's excerpt
        $excerpt = $entry->getExcerpt(150, array('fullExcerpt' => true));
        if (strlen($excerpt)) {
            $this->view->headMeta()->setName('description', $excerpt);
        }

        // set the contentEntry view helper defautls
        $view->contentEntry()->setDefaults(
            $entry,
            array(Content_View_Helper_ContentEntry::OPT_PRELOAD_FORM => true)
        );

        // record the content id in the widget context to assist any plugins
        // that need to know what content is currently being displayed.
        $this->widgetContext->setValue('contentId', $entry->getId());

        // select the layout and view scripts to use.
        if (!$this->contextSwitch->getCurrentContext()) {
            $this->getHelper('layout')->setLayout($this->_getEntryLayoutScript($entry));
        }
        $this->getHelper('viewRenderer')->setRender($this->_getEntryViewScript($entry));
    }

Member Data Documentation

Content_IndexController::$contexts
Initial value:
 array(
        'add'               => array('dojoio' => 'post', 'json' => 'post'),
        'browse'            => array('json', 'partial'),
        'edit'              => array('dojoio' => 'post', 'json' => 'post'),
        'choose-type'       => array('partial'),
        'delete'            => array('json', 'partial'),
        'form'              => array('partial', 'dojoio' => 'post'),
        'index'             => array('json'),
        'sub-form'          => array('partial'),
        'toolbar'           => array('partial'),
        'validate-field'    => array('json'),
        'view'              => array('json', 'preview'),
        'opened'            => array('json')
    )

The documentation for this class was generated from the following file: