/ */ class Workflow_Form_EditContent extends P4Cms_Form_SubForm { const E_INVALID_DATE = "Date is invalid or unset."; const E_INVALID_TIME = "Time is invalid or unset."; const E_INVALID_TIMEDATE = "Scheduled status changes must be in the future."; protected $_entry; protected $_workflow; /** * Defines the elements that make up the workflow form. * Called automatically when the form object is created. */ public function init() { // set the title of this form. $this->setLegend('Workflow'); $this->addElement( 'hidden', 'currentState', array( 'value' => $this->getCurrentState(), 'ignore' => true ) ); $this->addElement( 'radio', 'state', array( 'label' => 'Status', 'multiOptions' => $this->getStateOptions() ) ); $this->addElement( 'radio', 'scheduled', array( 'label' => 'Schedule Status Change', 'multiOptions' => array( 'false' => 'Now', 'true' => 'Specify Server Date and Time' ) ) ); $this->addElement( 'note', 'currentTime' ); $this->getElement('currentTime') ->getDecorator('htmlTag') ->setOption('class', 'current-time'); $sheduledElementName = $this->getElement('scheduled')->getFullyQualifiedName(); $this->addElement( 'dateTextBox', 'scheduledDate', array( 'datePattern' => 'MMM d, yyyy', 'constraints' => array( 'min' => date('Y-m-d') ) ) ); // prepare time options $options = array(); for ($h = 0; $h < 24; $h++) { $amPm = $h < 12 ? ' AM' : ' PM'; $key = sprintf('%02d:00', $h); $options[$key] = ($h % 12 ?: 12) . $amPm; } $this->addElement( 'select', 'scheduledTime', array( 'multiOptions' => $options ) ); // put time/date elements together $this->addDisplayGroup( array('scheduled', 'scheduledDate', 'scheduledTime', 'currentTime'), 'scheduleGroup', array( 'class' => 'scheduleGroup' ) ); } /** * Set content entry and workflow options. * * @param array $options Zend provides no documentation for this. * @return Workflow_Form_EditContent provides fluent interface. */ public function setOptions(array $options) { if (isset($options['workflow'])) { $this->_workflow = $options['workflow']; unset($options['workflow']); } if (isset($options['entry'])) { $this->_entry = $options['entry']; unset($options['entry']); } return parent::setOptions($options); } /** * Get the workflow model this form is for. * * @return Workflow_Model_Workflow the workflow model instance. * @throws Workflow_Exception if no workflow has been set */ public function getWorkflow() { if (!$this->_workflow instanceof Workflow_Model_Workflow) { throw new Workflow_Exception( "Cannot get workflow for workflow form. No workflow has been set." ); } return $this->_workflow; } /** * Get the content entry this form is for. * * @return P4Cms_Content the content entry associated with this workflow. * @throws Workflow_Exception if no content entry has been set */ public function getEntry() { if (!$this->_entry instanceof P4Cms_Content) { throw new Workflow_Exception( "Cannot get content entry for workflow form. No content entry has been set." ); } return $this->_entry; } /** * Return current state of the form entry. * * @return string id of the form's current state */ public function getCurrentState() { return $this->getWorkflow()->getStateOf($this->getEntry())->getId(); } /** * Get the valid state options for the associated entry. * If data is given, pass it along so that workflow conditions * can evaluate against the latest (pending) values. * * @param array|null $data optional - pending values to pass to condition * @return array the valid state options for the current entry/data */ public function getStateOptions($data = null) { // get the entry and workflow that this form is for. $entry = $this->getEntry(); $workflow = $this->getWorkflow(); // get the current state of the content entry. $state = $workflow->getStateOf($entry); // always include the current state. $options = array($state->getId() => $state->getLabel() . " (Current)"); // get options for status radio element, passing data // so conditions can evaluate against pending values. $transitions = $state->getValidTransitionsFor($entry, $data); if ($transitions->count()) { $options += array_combine( $transitions->invoke('getId'), $transitions->invoke('getLabel') ); } // mark scheduled transition if there is one $scheduledState = $workflow->getScheduledStateOf($entry); if ($scheduledState && array_key_exists($scheduledState->getId(), $options)) { $options[$scheduledState->getId()] .= " (Scheduled)"; } // put state options in the same order they are defined in the // workflow - if the workflow is roughly linear (e.g. draft, review, // published) and defined in that order, this will likely be a more // intuitive order than if we used the order transitions are defined. $stateOrder = array_flip(array_keys($workflow->getStates())); uksort( $options, function($a, $b) use ($stateOrder) { return $stateOrder[$a] - $stateOrder[$b]; } ); return $options; } /** * Extend parent to hide schedule display group if current state is selected initially. * * @param array $defaults Zend provides no documentation for this. * @return Zend_Form provides fluent interface. */ public function setDefaults(array $defaults) { // hide schedule group if state is current $currentState = $this->getCurrentState(); if (array_key_exists('state', $defaults) && $defaults['state'] === $currentState) { $this->getDisplayGroup('scheduleGroup')->setAttrib('class', 'hidden'); } return parent::setDefaults($defaults); } /** * Extend parent method to provide additional checks for date and time * elements as they are required only if transition is scheduled. * Also ensures that date and time values (if required) refer to the * time in the future. * * @param array $data values to check against * @return boolean true if form is valid, false otherwise */ public function isValid($data) { $valid = parent::isValid($data); // extract workflow field values from data // in some contexts, fields are under a 'workflow' array // in other contexts they are at the top level. $belongTo = $this->getElementsBelongTo(); $workflow = $belongTo ? isset($data[$belongTo]) ? $data[$belongTo] : array() : $data; // ensure date and time fields are filled if transition is scheduled if (isset($workflow['scheduled']) && $workflow['scheduled'] === 'true') { $date = $workflow['scheduledDate']; $time = $workflow['scheduledTime']; // ensure time and date fields are not blank if (!$date) { $this->getElement('currentTime')->addError(self::E_INVALID_DATE); $valid = false; } if (!$time) { $this->getElement('currentTime')->addError(self::E_INVALID_TIME); $valid = false; } // ensure that timedate refers to the future if ($valid) { $timestamp = strtotime($date . ' ' . $time); if ($timestamp < time()) { $this->getElement('currentTime')->addError(self::E_INVALID_TIMEDATE); $valid = false; } } } return $valid; } /** * Extend parent to set event attributes on some elements. This cannot be done * during the init as elements' fully qualified names may not be known at that time. * In some cases (e.g. when this is a sub-form of a non-array sub-form), * getFullyQualifiedName() method of Zend_Form_Element doesn't work. * * @param Zend_View_Interface $view Zend provides no documentation for this * @return string rendered form markup */ public function render(Zend_View_Interface $view = null) { $currentState = $this->getElement('currentState')->getName(); $scheduled = $this->getElement('scheduled')->getName(); $belongsTo = $this->getElementsBelongTo(); if ($belongsTo) { $currentState = $belongsTo . '[' . $currentState . ']'; $scheduled = $belongsTo . '[' . $scheduled . ']'; } $this->getElement('state')->setAttrib( "onClick", " var currentState = this.form.elements['$currentState']; if (currentState && this.value === currentState.value) { p4cms.ui.hide('fieldset-scheduleGroup'); dojo.forEach(this.form.elements['$scheduled'], function(node){ if (node.value === 'false') { node.checked = true; } }); } else { p4cms.ui.show('fieldset-scheduleGroup'); } " ); $this->getElement('scheduledDate')->setAttrib( "onClick", " var inputNode = this.valueNode || this.focusNode; dojo.forEach(inputNode.form.elements['$scheduled'], function(node){ if (node.value === 'true') { node.checked = true; } }); " ); $this->getElement('scheduledTime')->setAttrib( "onClick", " dojo.forEach(this.form.elements['$scheduled'], function(node){ if (node.value === 'true') { node.checked = true; } }); " ); return parent::render($view); } }