<commit_before><?php
/**
 * A WorkflowInstance is created whenever a user 'starts' a workflow. 
 * 
 * This 'start' is triggered automatically when the user clicks the relevant 
 * button (eg 'apply for approval'). This creates a standalone object
 * that maintains the state of the workflow process. 
 *
 * @method WorkflowDefinition Definition()
 * @method WorkflowActionInstance CurrentAction()
 * @method Member Initiator()
 *
 * @author  <EMAIL>
 * @license BSD License (http://silverstripe.org/bsd-license/)
 * @package advancedworkflow
 */
class WorkflowInstance extends DataObject {

	private static $db = array(
		'Title'				=> 'Varchar(128)',
		'WorkflowStatus'	=> "Enum('Active,Paused,Complete,Cancelled','Active')",
		'TargetClass'		=> 'Varchar(64)',
		'TargetID'			=> 'Int',
	);

	private static $has_one = array(
		'Definition'    => 'WorkflowDefinition',
		'CurrentAction' => 'WorkflowActionInstance',
		'Initiator'		=> 'Member',
	);

	private static $has_many = array(
		'Actions'		=> 'WorkflowActionInstance',
	);

	/**
	 * The list of users who are responsible for performing the current WorkflowAction
	 *
	 * @var array
	 */
	private static $many_many = array(
		'Users'			=> 'Member',
		'Groups'		=> 'Group'
	);

	private static $summary_fields = array(
		'Title',
		'WorkflowStatus',
		'Created'
	);
	
	/**
	 * Get the CMS view of the instance. This is used to display the log of 
	 * this workflow, and options to reassign if the workflow hasn't been 
	 * finished yet
	 * 
	 * @return \FieldList 
	 */
	public function getCMSFields() {
		$fields = new FieldList();
		
		if (Permission::check('REASSIGN_ACTIVE_WORKFLOWS')) {
			if ($this->WorkflowStatus == 'Paused' || $this->WorkflowStatus == 'Active') {
				$cmsUsers = Member::mapInCMSGroups();

				$fields->push(new HiddenField('DirectUpdate', '', 1));

				$fields->push(new HeaderField('InstanceReassignHeader',_t('WorkflowInstance.REASSIGN_HEADER', 'Reassign workflow')));
				$fields->push(new CheckboxSetField('Users', _t('WorkflowDefinition.USERS', 'Users'), $cmsUsers));
				$fields->push(new TreeMultiselectField('Groups', _t('WorkflowDefinition.GROUPS', 'Groups'), 'Group'));

			}
		}
		
		if ($this->canEdit()) {
			$action = $this->CurrentAction();
			if ($action->exists()) {
				$actionFields = $this->getWorkflowFields();
				$fields->merge($actionFields);
				
				$transitions = $action->getValidTransitions();
				if ($transitions) {
					$fields->replaceField('TransitionID', DropdownField::create("TransitionID", "Next action", $transitions->map()));
				}
			}
		}

		$items = WorkflowActionInstance::get()->filter(array(
			'Finished'		=> 1,
			'WorkflowID'	=> $this->ID
		));

		$grid = new GridField(
			'Actions', 
			_t('WorkflowInstance.ActionLogTitle','Log'), 
			$items
		);

		$fields->push($grid);

		return $fields;
	}
	
	public function fieldLabels($includerelations = true) {
		$labels = parent::fieldLabels($includerelations);
		$labels['Title'] = _t('WorkflowInstance.TitleLabel', 'Title');
		$labels['WorkflowStatus'] = _t('WorkflowInstance.WorkflowStatusLabel', 'Workflow Status');
		$labels['TargetClass'] = _t('WorkflowInstance.TargetClassLabel', 'Target Class');
		$labels['TargetID'] = _t('WorkflowInstance.TargetIDLabel', 'Target');

		return $labels;
	}
	
	/**
	 * See if we've been saved in context of managing the workflow directly
	 */
	public function onBeforeWrite() {
		parent::onBeforeWrite();
		
		$vars = $this->record;

		if (isset($vars['DirectUpdate'])) {
			// Unset now so that we don't end up in an infinite loop!
			unset($this->record['DirectUpdate']);
			$this->updateWorkflow($vars);
		}
	}
	
	/**
	 * Update the current state of the workflow
	 * 
	 * Typically, this is triggered by someone modifiying the workflow instance via the modeladmin form
	 * side of things when administering things, such as re-assigning or manually approving a stuck workflow
	 * 
	 * Note that this is VERY similar to AdvancedWorkflowExtension::updateworkflow
	 * but without the formy bits. These two implementations should PROBABLY
	 * be merged
	 * 
	 * @todo refactor with AdvancedWorkflowExtension
	 *
	 * @param type $data
	 * @return 
	 */
	public function updateWorkflow($data) {
		$action = $this->CurrentAction();

		if (!$this->getTarget() || !$this->getTarget()->canEditWorkflow()) {
			return;
		}

		$allowedFields = $this->getWorkflowFields()->saveableFields();
		unset($allowedFields['TransitionID']);
		foreach ($allowedFields as $field) {
			$fieldName = $field->getName();
			$action->$fieldName = $data[$fieldName];
		}
		$action->write();

		$svc = singleton('WorkflowService');
		if (isset($data['TransitionID']) && $data['TransitionID']) {
			$svc->executeTransition($this->getTarget(), $data['TransitionID']);
		} else {
			// otherwise, just try to execute the current workflow to see if it
			// can now proceed based on user input
			$this->execute();
		}
	}

	/**
	 * Get the target-object that this WorkflowInstance "points" to.
	 *
	 * Workflows are not restricted to being active on SiteTree objects,
	 * so we need to account for being attached to anything.
	 *
	 * Sets Versioned::set_reading_mode() to allow fetching of Draft _and_ Published
	 * content.
	 *
	 * @return (null | DataObject)
	 */
	public function getTarget() {
		if($this->TargetID && $this->TargetClass) {
			$versionable = Injector::inst()->get($this->TargetClass)->has_extension('Versioned');
			if($versionable) {
				Versioned::set_reading_mode("Stage.Stage");
			}

			// Default
			return DataObject::get_by_id($this->TargetClass, $this->TargetID);
		}
	}

	/**
	 * 
	 * @see {@link {$this->getTarget()}
	 * @return (null | DataObject)
	 */
	public function Target() {
		return $this->getTarget();
	}

	/**
	 * Start a workflow based on a particular definition for a particular object.
	 *
	 * The object is optional; if not specified, it is assumed that this workflow
	 * is simply a task based checklist type of workflow.
	 *
	 * @param WorkflowDefinition $definition
	 * @param DataObject $for
	 */
	public function beginWorkflow(WorkflowDefinition $definition, DataObject $for=null) {
		if(!$this->ID) {
			$this->write();
		}

		if ($for && ($for->hasExtension('WorkflowApplicable') || $for->hasExtension('FileWorkflowApplicable'))) {<commit_msg>set the stage back after fetching the target<commit_after><?php
/**
 * A WorkflowInstance is created whenever a user 'starts' a workflow. 
 * 
 * This 'start' is triggered automatically when the user clicks the relevant 
 * button (eg 'apply for approval'). This creates a standalone object
 * that maintains the state of the workflow process. 
 *
 * @method WorkflowDefinition Definition()
 * @method WorkflowActionInstance CurrentAction()
 * @method Member Initiator()
 *
 * @author  <EMAIL>
 * @license BSD License (http://silverstripe.org/bsd-license/)
 * @package advancedworkflow
 */
class WorkflowInstance extends DataObject {

	private static $db = array(
		'Title'				=> 'Varchar(128)',
		'WorkflowStatus'	=> "Enum('Active,Paused,Complete,Cancelled','Active')",
		'TargetClass'		=> 'Varchar(64)',
		'TargetID'			=> 'Int',
	);

	private static $has_one = array(
		'Definition'    => 'WorkflowDefinition',
		'CurrentAction' => 'WorkflowActionInstance',
		'Initiator'		=> 'Member',
	);

	private static $has_many = array(
		'Actions'		=> 'WorkflowActionInstance',
	);

	/**
	 * The list of users who are responsible for performing the current WorkflowAction
	 *
	 * @var array
	 */
	private static $many_many = array(
		'Users'			=> 'Member',
		'Groups'		=> 'Group'
	);

	private static $summary_fields = array(
		'Title',
		'WorkflowStatus',
		'Created'
	);
	
	/**
	 * Get the CMS view of the instance. This is used to display the log of 
	 * this workflow, and options to reassign if the workflow hasn't been 
	 * finished yet
	 * 
	 * @return \FieldList 
	 */
	public function getCMSFields() {
		$fields = new FieldList();
		
		if (Permission::check('REASSIGN_ACTIVE_WORKFLOWS')) {
			if ($this->WorkflowStatus == 'Paused' || $this->WorkflowStatus == 'Active') {
				$cmsUsers = Member::mapInCMSGroups();

				$fields->push(new HiddenField('DirectUpdate', '', 1));

				$fields->push(new HeaderField('InstanceReassignHeader',_t('WorkflowInstance.REASSIGN_HEADER', 'Reassign workflow')));
				$fields->push(new CheckboxSetField('Users', _t('WorkflowDefinition.USERS', 'Users'), $cmsUsers));
				$fields->push(new TreeMultiselectField('Groups', _t('WorkflowDefinition.GROUPS', 'Groups'), 'Group'));

			}
		}
		
		if ($this->canEdit()) {
			$action = $this->CurrentAction();
			if ($action->exists()) {
				$actionFields = $this->getWorkflowFields();
				$fields->merge($actionFields);
				
				$transitions = $action->getValidTransitions();
				if ($transitions) {
					$fields->replaceField('TransitionID', DropdownField::create("TransitionID", "Next action", $transitions->map()));
				}
			}
		}

		$items = WorkflowActionInstance::get()->filter(array(
			'Finished'		=> 1,
			'WorkflowID'	=> $this->ID
		));

		$grid = new GridField(
			'Actions', 
			_t('WorkflowInstance.ActionLogTitle','Log'), 
			$items
		);

		$fields->push($grid);

		return $fields;
	}
	
	public function fieldLabels($includerelations = true) {
		$labels = parent::fieldLabels($includerelations);
		$labels['Title'] = _t('WorkflowInstance.TitleLabel', 'Title');
		$labels['WorkflowStatus'] = _t('WorkflowInstance.WorkflowStatusLabel', 'Workflow Status');
		$labels['TargetClass'] = _t('WorkflowInstance.TargetClassLabel', 'Target Class');
		$labels['TargetID'] = _t('WorkflowInstance.TargetIDLabel', 'Target');

		return $labels;
	}
	
	/**
	 * See if we've been saved in context of managing the workflow directly
	 */
	public function onBeforeWrite() {
		parent::onBeforeWrite();
		
		$vars = $this->record;

		if (isset($vars['DirectUpdate'])) {
			// Unset now so that we don't end up in an infinite loop!
			unset($this->record['DirectUpdate']);
			$this->updateWorkflow($vars);
		}
	}
	
	/**
	 * Update the current state of the workflow
	 * 
	 * Typically, this is triggered by someone modifiying the workflow instance via the modeladmin form
	 * side of things when administering things, such as re-assigning or manually approving a stuck workflow
	 * 
	 * Note that this is VERY similar to AdvancedWorkflowExtension::updateworkflow
	 * but without the formy bits. These two implementations should PROBABLY
	 * be merged
	 * 
	 * @todo refactor with AdvancedWorkflowExtension
	 *
	 * @param type $data
	 * @return 
	 */
	public function updateWorkflow($data) {
		$action = $this->CurrentAction();

		if (!$this->getTarget() || !$this->getTarget()->canEditWorkflow()) {
			return;
		}

		$allowedFields = $this->getWorkflowFields()->saveableFields();
		unset($allowedFields['TransitionID']);
		foreach ($allowedFields as $field) {
			$fieldName = $field->getName();
			$action->$fieldName = $data[$fieldName];
		}
		$action->write();

		$svc = singleton('WorkflowService');
		if (isset($data['TransitionID']) && $data['TransitionID']) {
			$svc->executeTransition($this->getTarget(), $data['TransitionID']);
		} else {
			// otherwise, just try to execute the current workflow to see if it
			// can now proceed based on user input
			$this->execute();
		}
	}

	/**
	 * Get the target-object that this WorkflowInstance "points" to.
	 *
	 * Workflows are not restricted to being active on SiteTree objects,
	 * so we need to account for being attached to anything.
	 *
	 * Sets Versioned::set_reading_mode() to allow fetching of Draft _and_ Published
	 * content.
	 *
	 * @return (null | DataObject)
	 */
	public function getTarget() {
		if($this->TargetID && $this->TargetClass) {
			$versionable = Injector::inst()->get($this->TargetClass)->has_extension('Versioned');
			$stage = Versioned::current_stage();
			if($versionable) {
				Versioned::set_reading_mode("Stage.Stage");
			}

			$target = DataObject::get_by_id($this->TargetClass, $this->TargetID);
            		Versioned::set_reading_mode("Stage.".$stage);

			// Default
			return $target;
		}
	}

	/**
	 * 
	 * @see {@link {$this->getTarget()}
	 * @return (null | DataObject)
	 */
	public function Target() {
		return $this->getTarget();
	}

	/**
	 * Start a workflow based on a particular definition for a particular object.
	 *
	 * The object is optional; if not specified, it is assumed that this workflow
	 * is simply a task based checklist type of workflow.
	 *
	 * @param WorkflowDefinition $definition
	 * @param DataObject $for
	 */
	public function beginWorkflow(WorkflowDefinition $definition, DataObject $for=null) {
		if(!$this->ID) {
			$this->write();
		}

		if ($for && ($for->hasExtension('WorkflowApplicable') || $for->hasExtension('FileWorkflowApplicable'))) {
{'fraction_non_alphanumeric': 0.14922907488986784, 'fraction_numerical': 0.0011013215859030838, 'mean_word_length': 4.167886178861789, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 119, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5200386', 'n_tokens_mistral': 4353, 'n_tokens_neox': 3756, 'n_words': 1010}