<commit_before><?php
///////////////////////////////////////////////////////////////////////////////
//
// © Copyright f-project.net 2010-present. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////

namespace fproject\workflow\core;

use fproject\workflow\events\WorkflowEvent;
use Yii;
use yii\base\Behavior;
use yii\base\Model;
use yii\base\ModelEvent;
use yii\base\InvalidConfigException;
use yii\base\Exception;
use fproject\workflow\events\IEventSequence;
use fproject\workflow\helpers\WorkflowScenario;
use yii\db\BaseActiveRecord;

/**
 * ActiveWorkflowBehavior implements the behavior of db model evolving inside a workflow.
 *
 * To use ActiveWorkflowBehavior with the default parameters, simply attach it to the model class.
 * ~~~
 * use fproject\workflow\core\ActiveWorkflowBehavior;
 *
 * public function behaviors()
 * {
 *     return [
 *         'workflow' => [
 *             'class' => ActiveWorkflowBehavior::className()
 *         ],
 *     ];
 * }
 * ~~~
 *
 * You can customize the ActiveWorkflowBehavior with the following parameters :
 *
 * - statusAttribute : name of the attribute that is used by the owner model to hold the status value. The
 * default value is "status".
 * - workflow : identifier of the default workflow for the owner model. If no value is provided, the behavior
 * creates a default workflow identifier (see  ActiveWorkflowBehavior#getDefaultWorkflowId)
 * - factory : name of the workflow factory component that the behavior must use to read the workflow. By default
 * the Yii application component "workflowFactory" is used and if it is not already available it is created by the
 * behavior using the default workflow factory component class.
 *
 *
 * Below is an example behavior initialization :
 * ~~~
 * use fproject\workflow\core\ActiveWorkflowBehavior;
 *
 * public function behaviors()
 * {
 *     return [
 *         'workflow' => [
 *             'class' => ActiveWorkflowBehavior::className(),
 *             'statusAttribute' => 'col_status',
 *             'defaultWorkflowId' => 'MyWorkflow',
 *             'factory' => 'arrayWorkflowItemFactory',
 *             'statusConverter' => 'myStatusConverter',
 *             'eventSequence' => 'myCustomEventSequence',
 *         ],
 *     ];
 * }
 * ~~~
 * Please note that the model must be an instance of yii\base\Model.
 *
 * @property Model owner
 * @property IStatus workflowStatus
 */
class ActiveWorkflowBehavior extends Behavior
{
	const DEFAULT_FACTORY_CLASS = 'fproject\workflow\core\ArrayWorkflowItemFactory';
	const DEFAULT_EVENT_SEQUENCE_CLASS = 'fproject\workflow\events\BasicEventSequence';

	/**
	 * @var string name of the owner model attribute used to store the current status value. It is also possible
	 * to use a model property but in this case you must provide a suitable status accessor component that will handle
	 * status persistence.
	 */
	public $statusAttribute = 'status';

	/**
	 * @var string name of the workflow factory component to use with the behavior
	 */
	public $factoryName = 'workflowFactory';

	/**
	 * @var string name of an existing status Id converter component, to use with this behavior. When NULL, no status converter
	 * component will be used.
	 */
	public $statusConverter = null;

	/**
	 * @var string Name of the status accessor component used by this behavior to set/get status values. By default, no external
	 * accessor is used and the behavior directly access the the status attribute in the owner model.
	 */
	public $statusAccessor = null;

    /**
     * @var string Name of the workflow ID accessor component used by this behavior to set/get workflow ID values. By default, no external
     * accessor is used and the behavior the class name of owner model concat with the suffix 'Workflow'.
     */
    public $idAccessor = null;

	/**
	 * @var string name of the event sequence provider component. If the component does not exist it is created
	 * by this behavior using the default event sequence class.
	 * Set this attribute to NULL if you are not going to use any Workflow Event.
	 */
	public $eventSequence = 'eventSequence';

	/**
	 * @var bool|string if TRUE, the model is automatically inserted into the default workflow. If 
	 * $autoInsert contains a string, it is assumed to be an initial status Id that will be used to set the 
	 * status. If FALSE (default) the status is not modified. 
	 */
	public $autoInsert = false;

	/**
	 * @var string Read only property that contains the id of the default workflow to use with
	 * this behavior.
	 */
	private $_defaultWorkflowId;

	/**
	 * @property IStatus|null Internal value of the owner model status. This is the real value of the owner model status. It is
	 * maintained internally, depending on the path the owner model is going through within a workflow.
	 * Use getWorkflowStatus() to get the actual Status instance.
	 */
	private $_workflowStatus = null;

	/**
	 * @var IStatusIdConverter Instance of the status ID converter used by this behavior or NULL if no status conversion is done
	 */
	private $_statusConverter = null;

	/**
	 * @var IWorkflowItemFactory reference to the workflow factory component used by this behavior
	 */
	private $_workflowFactory;

	/**
	 * @var array workflow events that are fired after save
	 */
	private $_pendingEvents = [];

	/**
	 * @var IEventSequence|null the Event sequence component that provides events to this behavior. If NULL, no event will be
	 * fired by this behavior.
	 */
	private $_eventSequence = null;

	/**
	 * @var IStatusAccessor|null the status accessor component used by this behavior or NULL if no such component is used.
	 */
	private $_statusAccessor = null;

    /**
     * @var IIdAccessor|null the ID accessor component used by this behavior or NULL if no such component is used.
     */
    private $_idAccessor = null;

    /**
     * @param array $config
     * @throws InvalidConfigException
     */
	public function __construct($config = [])
	{
		if (array_key_exists('defaultWorkflowId', $config)) {
			if (is_string($config['defaultWorkflowId'])) {
				$this->_defaultWorkflowId = $config['defaultWorkflowId'];
			} else {
				throw new InvalidConfigException("Invalid property Type : 'defaultWorkflowId' must be a string");
			}
			unset($config['defaultWorkflowId']);
		}
		parent::__construct($config);
	}

	/**
	 * At initialization time, following actions are taken :
	 * - perform validation
	 * - get a reference to the workflow factory component. If it doesn't exist, it is created.
	 * - get a reference to the event model component or create it if needed
	 * - get a reference to the status converter component
	 * - get a reference to the status accessor component
	 *  
	 * @see \yii\base\Object::init()
	 */
	public function init()
	{
		parent::init();

		if (empty($this->statusAttribute)) {
			throw new InvalidConfigException('The "statusAttribute" configuration for the Behavior is required.');
		}

		// init source
		if (empty($this->factoryName)) {
			throw new InvalidConfigException('The "factoryName" configuration for the Behavior can\'t be empty.');
		} elseif (!Yii::$app->has($this->factoryName)) {
			Yii::$app->set($this->factoryName, ['class'=> self::DEFAULT_FACTORY_CLASS]);
		}
		$this->_workflowFactory = Yii::$app->get($this->factoryName);

		// init Event Sequence
		if ($this->eventSequence == null) {
			$this->_eventSequence = null;
		} elseif (is_string($this->eventSequence)) {
			if (!Yii::$app->has($this->eventSequence)) {
				Yii::$app->set($this->eventSequence, ['class'=> self::DEFAULT_EVENT_SEQUENCE_CLASS]);
			}
			$this->_eventSequence = Yii::$app->get($this->eventSequence);
		} else {
			throw new InvalidConfigException('Invalid "eventSequence" value.');
		}

		// init status converter
		if (!empty($this->statusConverter)) {
			$this->_statusConverter = Yii::$app->get($this->statusConverter);
		}

		// init status accessor
		if (!empty($this->statusAccessor)) {
			$this->_statusAccessor = Yii::$app->get($this->statusAccessor);
		}

        // init status accessor
        if (!empty($this->idAccessor)) {
            $this->_idAccessor = Yii::$app->get($this->idAccessor);
        }
	}

    /**
     * Attaches the behavior to the model.
     *
     * This method is automatically called by Yii. The behavior can successfully be attached to the model if :
     *
     * - the model is an instance of yii\base\Model
     * - the model has a attribute to hold the status value. The name of this attribute can be configured  when the behavior
     * is created (see statusAttribute). By Default the status attribute name is 'status'. Note that a property name can also be used
     * but in this case you must provide a suitable Status Accessor component to handle status persistence.
     *
     * If previous requirements are met, the internal status value is initialized.
     * @param Model $owner
     * @throws InvalidConfigException
     * @throws WorkflowException
     * @see \yii\base\Behavior::attach()
     * @see \fproject\workflow\core\ActiveWorkflowBehavior::initStatus()
     */
	public function attach($owner)
	{
		parent::attach($owner);
		if (!($this->owner instanceof Model)) {
			throw new InvalidConfigException('The attached model is not an instance of yii\base\Model ('.get_class($this->owner).')');
		}

        if (!$this->owner->hasProperty($this->statusAttribute)) {
            if($this->owner instanceof BaseActiveRecord && $this->owner->hasAttribute($this->statusAttribute))
            {
            }
            else
            {
                throw new InvalidConfigException('Attribute or property not found for owner model : \''.$this->statusAttribute.'\'');
            }
        }

		$this->initStatus();
		if(!$this->hasWorkflowStatus()) {
			$this->doAutoInsert();
		}
	}

	/**
	 * (non-PHPDoc)
	 * @see \yii\base\Behavior::events()
	 */
	public function events()
	{
        if($this->owner instanceof BaseActiveRecord)
        {
            return [
                BaseActiveRecord::EVENT_AFTER_FIND 		=> 'initStatus',
                BaseActiveRecord::EVENT_BEFORE_INSERT 	=> 'beforeSaveStatus',
                BaseActiveRecord::EVENT_BEFORE_UPDATE 	=> 'beforeSaveStatus',
                BaseActiveRecord::EVENT_AFTER_UPDATE 	=> 'afterSaveStatus',
                BaseActiveRecord::EVENT_AFTER_INSERT 	=> 'afterSaveStatus',
            ];
        }
		return [];
	}
<commit_msg>Update ActiveWorkflowBehavior:  add event listener for 'afterLoad' event<commit_after><?php
///////////////////////////////////////////////////////////////////////////////
//
// © Copyright f-project.net 2010-present. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////

namespace fproject\workflow\core;

use fproject\workflow\events\WorkflowEvent;
use Yii;
use yii\base\Behavior;
use yii\base\Model;
use yii\base\ModelEvent;
use yii\base\InvalidConfigException;
use yii\base\Exception;
use fproject\workflow\events\IEventSequence;
use fproject\workflow\helpers\WorkflowScenario;
use yii\db\BaseActiveRecord;

/**
 * ActiveWorkflowBehavior implements the behavior of db model evolving inside a workflow.
 *
 * To use ActiveWorkflowBehavior with the default parameters, simply attach it to the model class.
 * ~~~
 * use fproject\workflow\core\ActiveWorkflowBehavior;
 *
 * public function behaviors()
 * {
 *     return [
 *         'workflow' => [
 *             'class' => ActiveWorkflowBehavior::className()
 *         ],
 *     ];
 * }
 * ~~~
 *
 * You can customize the ActiveWorkflowBehavior with the following parameters :
 *
 * - statusAttribute : name of the attribute that is used by the owner model to hold the status value. The
 * default value is "status".
 * - workflow : identifier of the default workflow for the owner model. If no value is provided, the behavior
 * creates a default workflow identifier (see  ActiveWorkflowBehavior#getDefaultWorkflowId)
 * - factory : name of the workflow factory component that the behavior must use to read the workflow. By default
 * the Yii application component "workflowFactory" is used and if it is not already available it is created by the
 * behavior using the default workflow factory component class.
 *
 *
 * Below is an example behavior initialization :
 * ~~~
 * use fproject\workflow\core\ActiveWorkflowBehavior;
 *
 * public function behaviors()
 * {
 *     return [
 *         'workflow' => [
 *             'class' => ActiveWorkflowBehavior::className(),
 *             'statusAttribute' => 'col_status',
 *             'defaultWorkflowId' => 'MyWorkflow',
 *             'factory' => 'arrayWorkflowItemFactory',
 *             'statusConverter' => 'myStatusConverter',
 *             'eventSequence' => 'myCustomEventSequence',
 *         ],
 *     ];
 * }
 * ~~~
 * Please note that the model must be an instance of yii\base\Model.
 *
 * @property Model owner
 * @property IStatus workflowStatus
 */
class ActiveWorkflowBehavior extends Behavior
{
	const DEFAULT_FACTORY_CLASS = 'fproject\workflow\core\ArrayWorkflowItemFactory';
	const DEFAULT_EVENT_SEQUENCE_CLASS = 'fproject\workflow\events\BasicEventSequence';

	/**
	 * @var string name of the owner model attribute used to store the current status value. It is also possible
	 * to use a model property but in this case you must provide a suitable status accessor component that will handle
	 * status persistence.
	 */
	public $statusAttribute = 'status';

	/**
	 * @var string name of the workflow factory component to use with the behavior
	 */
	public $factoryName = 'workflowFactory';

	/**
	 * @var string name of an existing status Id converter component, to use with this behavior. When NULL, no status converter
	 * component will be used.
	 */
	public $statusConverter = null;

	/**
	 * @var string Name of the status accessor component used by this behavior to set/get status values. By default, no external
	 * accessor is used and the behavior directly access the the status attribute in the owner model.
	 */
	public $statusAccessor = null;

    /**
     * @var string Name of the workflow ID accessor component used by this behavior to set/get workflow ID values. By default, no external
     * accessor is used and the behavior the class name of owner model concat with the suffix 'Workflow'.
     */
    public $idAccessor = null;

	/**
	 * @var string name of the event sequence provider component. If the component does not exist it is created
	 * by this behavior using the default event sequence class.
	 * Set this attribute to NULL if you are not going to use any Workflow Event.
	 */
	public $eventSequence = 'eventSequence';

	/**
	 * @var bool|string if TRUE, the model is automatically inserted into the default workflow. If 
	 * $autoInsert contains a string, it is assumed to be an initial status Id that will be used to set the 
	 * status. If FALSE (default) the status is not modified. 
	 */
	public $autoInsert = false;

	/**
	 * @var string Read only property that contains the id of the default workflow to use with
	 * this behavior.
	 */
	private $_defaultWorkflowId;

	/**
	 * @property IStatus|null Internal value of the owner model status. This is the real value of the owner model status. It is
	 * maintained internally, depending on the path the owner model is going through within a workflow.
	 * Use getWorkflowStatus() to get the actual Status instance.
	 */
	private $_workflowStatus = null;

	/**
	 * @var IStatusIdConverter Instance of the status ID converter used by this behavior or NULL if no status conversion is done
	 */
	private $_statusConverter = null;

	/**
	 * @var IWorkflowItemFactory reference to the workflow factory component used by this behavior
	 */
	private $_workflowFactory;

	/**
	 * @var array workflow events that are fired after save
	 */
	private $_pendingEvents = [];

	/**
	 * @var IEventSequence|null the Event sequence component that provides events to this behavior. If NULL, no event will be
	 * fired by this behavior.
	 */
	private $_eventSequence = null;

	/**
	 * @var IStatusAccessor|null the status accessor component used by this behavior or NULL if no such component is used.
	 */
	private $_statusAccessor = null;

    /**
     * @var IIdAccessor|null the ID accessor component used by this behavior or NULL if no such component is used.
     */
    private $_idAccessor = null;

    /**
     * @param array $config
     * @throws InvalidConfigException
     */
	public function __construct($config = [])
	{
		if (array_key_exists('defaultWorkflowId', $config)) {
			if (is_string($config['defaultWorkflowId'])) {
				$this->_defaultWorkflowId = $config['defaultWorkflowId'];
			} else {
				throw new InvalidConfigException("Invalid property Type : 'defaultWorkflowId' must be a string");
			}
			unset($config['defaultWorkflowId']);
		}
		parent::__construct($config);
	}

	/**
	 * At initialization time, following actions are taken :
	 * - perform validation
	 * - get a reference to the workflow factory component. If it doesn't exist, it is created.
	 * - get a reference to the event model component or create it if needed
	 * - get a reference to the status converter component
	 * - get a reference to the status accessor component
	 *  
	 * @see \yii\base\Object::init()
	 */
	public function init()
	{
		parent::init();

		if (empty($this->statusAttribute)) {
			throw new InvalidConfigException('The "statusAttribute" configuration for the Behavior is required.');
		}

		// init source
		if (empty($this->factoryName)) {
			throw new InvalidConfigException('The "factoryName" configuration for the Behavior can\'t be empty.');
		} elseif (!Yii::$app->has($this->factoryName)) {
			Yii::$app->set($this->factoryName, ['class'=> self::DEFAULT_FACTORY_CLASS]);
		}
		$this->_workflowFactory = Yii::$app->get($this->factoryName);

		// init Event Sequence
		if ($this->eventSequence == null) {
			$this->_eventSequence = null;
		} elseif (is_string($this->eventSequence)) {
			if (!Yii::$app->has($this->eventSequence)) {
				Yii::$app->set($this->eventSequence, ['class'=> self::DEFAULT_EVENT_SEQUENCE_CLASS]);
			}
			$this->_eventSequence = Yii::$app->get($this->eventSequence);
		} else {
			throw new InvalidConfigException('Invalid "eventSequence" value.');
		}

		// init status converter
		if (!empty($this->statusConverter)) {
			$this->_statusConverter = Yii::$app->get($this->statusConverter);
		}

		// init status accessor
		if (!empty($this->statusAccessor)) {
			$this->_statusAccessor = Yii::$app->get($this->statusAccessor);
		}

        // init status accessor
        if (!empty($this->idAccessor)) {
            $this->_idAccessor = Yii::$app->get($this->idAccessor);
        }
	}

    /**
     * Attaches the behavior to the model.
     *
     * This method is automatically called by Yii. The behavior can successfully be attached to the model if :
     *
     * - the model is an instance of yii\base\Model
     * - the model has a attribute to hold the status value. The name of this attribute can be configured  when the behavior
     * is created (see statusAttribute). By Default the status attribute name is 'status'. Note that a property name can also be used
     * but in this case you must provide a suitable Status Accessor component to handle status persistence.
     *
     * If previous requirements are met, the internal status value is initialized.
     * @param Model $owner
     * @throws InvalidConfigException
     * @throws WorkflowException
     * @see \yii\base\Behavior::attach()
     * @see \fproject\workflow\core\ActiveWorkflowBehavior::initStatus()
     */
	public function attach($owner)
	{
		parent::attach($owner);
		if (!($this->owner instanceof Model)) {
			throw new InvalidConfigException('The attached model is not an instance of yii\base\Model ('.get_class($this->owner).')');
		}

        if (!$this->owner->hasProperty($this->statusAttribute)) {
            if($this->owner instanceof BaseActiveRecord && $this->owner->hasAttribute($this->statusAttribute))
            {
				/**
				 * @todo In case of using status proxy
				 * */
            }
            else
            {
                throw new InvalidConfigException('Attribute or property not found for owner model : \''.$this->statusAttribute.'\'');
            }
        }

		$this->initStatus();
		if(!$this->hasWorkflowStatus()) {
			$this->doAutoInsert();
		}
	}

	/**
	 * (non-PHPDoc)
	 * @see \yii\base\Behavior::events()
	 */
	public function events()
	{
        if($this->owner instanceof BaseActiveRecord)
        {
            return [
				'afterLoad' => 'initStatus',
                BaseActiveRecord::EVENT_AFTER_FIND 		=> 'initStatus',
                BaseActiveRecord::EVENT_BEFORE_INSERT 	=> 'beforeSaveStatus',
                BaseActiveRecord::EVENT_BEFORE_UPDATE 	=> 'beforeSaveStatus',
                BaseActiveRecord::EVENT_AFTER_UPDATE 	=> 'afterSaveStatus',
                BaseActiveRecord::EVENT_AFTER_INSERT 	=> 'afterSaveStatus',
            ];
        }
		return [];
	}

{'fraction_non_alphanumeric': 0.11620877881535616, 'fraction_numerical': 0.000721793657238237, 'mean_word_length': 3.0983545941948605, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 128, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '3307453', 'n_tokens_mistral': 6094, 'n_tokens_neox': 5735, 'n_words': 2160}