<commit_before><?php
/*"******************************************************************************************************
*   (c) 2004-2006 by MulchProductions, www.mulchprod.de                                                 *
*   (c) 2007-2015 by Kajona, www.kajona.de                                                              *
*       Published under the GNU LGPL v2.1, see /system/licence_lgpl.txt                                 *
*-------------------------------------------------------------------------------------------------------*
*   $Id$                                              *
********************************************************************************************************/

/**
 * The top-level class for models, installers and top-level files
 * An instance of this class is used by the admin & portal object to invoke common database based methods.
 * Change with care!
 *
 * @package module_system
 * @author <EMAIL>
 */
abstract class class_root {

    const STR_MODULE_ANNOTATION = "@module";
    const STR_MODULEID_ANNOTATION = "@moduleId";


    /**
     * Instance of class_config
     *
     * @var class_config
     */
    protected $objConfig = null; //Object containing config-data
    /**
     * Instance of class_db
     *
     * @var class_db
     */
    protected $objDB = null; //Object to the database
    /**
     * Instance of class_session
     *
     * @var class_session
     */
    protected $objSession = null; //Object containing the session-management

    /**
     * Instance of class_lang
     *
     * @var class_lang
     */
    private $objLang = null; //Object managing the langfiles

    /**
     * @var interface_sortmanager
     */
    protected $objSortManager = null;

    private $strAction; //current action to perform (GET/POST)
    protected $arrModule = array(); //Array containing information about the current module


    private $arrInitRow = null;             //array to be used when loading details from the database. could reduce the amount of queries if populated.

    //--- fields to be synchronized with the database ---

    /**
     * The records current systemid
     * @var string
     * @templateExport
     *
     * @tableColumn system.system_id
     */
    private $strSystemid = "";

    /**
     * The records internal parent-id
     * @var string
     * @versionable
     *
     * @tableColumn system.system_prev_id
     */
    private $strPrevId = -1;

    /**
     * The old prev-id, used to track hierarchical changes -> requires a rebuild of the rights-table
     * @var string
     */
    private $strOldPrevId = -1;

    /**
     * The records module-number
     * @var int
     * @tableColumn system.system_module_nr
     */
    private $intModuleNr = 0;

    /**
     * The records sort-position relative to the parent record
     * @var int
     * @tableColumn system.system_sort
     */
    private $intSort = -1;

    /**
     * The id of the user who created the record initially
     * @var string
     * @versionable
     * @templateExport
     * @templateMapper user
     * @tableColumn system.system_owner
     */
    private $strOwner = "";

    /**
     * The id of the user last who did the last changes to the current record
     * @var string
     * @tableColumn system.system_lm_user
     */
    private $strLmUser = "";

    /**
     * Timestamp of the last modification
     * ATTENTION: time() based, so 32 bit integer
     * @todo migrate to long-timestamp
     * @var int
     * @templateExport
     * @templateMapper datetime
     * @tableColumn system.system_lm_time
     */
    private $intLmTime = 0;

    /**
     * The id of the user locking the current record, emtpy otherwise
     * @var string
     * @tableColumn system.system_lock_id
     */
    private $strLockId = "";

    /**
     * Time the current locking was triggered
     * ATTENTION: time() based, so 32 bit integer
     * @todo migrate to long-timestamp
     * @var int
     * @tableColumn system.system_lock_time
     */
    private $intLockTime = 0;

    /**
     * The records status
     * @var int
     * @versionable
     * @tableColumn system.system_status
     */
    private $intRecordStatus = 1;

    /**
     * Indicates whether the object is deleted, or not
     * @var int
     * @versionable
     * @tableColumn system.system_deleted
     */
    private $intRecordDeleted = 0;

    /**
     * Human readable comment describing the current record
     * @var string
     * @tableColumn system.system_comment
     */
    private $strRecordComment = "";

    /**
     * Holds the current objects' class
     * @var string
     * @tableColumn system.system_class
     */
    private $strRecordClass = "";

    /**
     * Long-based representation of the timestamp the record was created initially
     * @var int
     * @templateExport
     * @templateMapper datetime
     * @tableColumn system.system_create_date
     */
    private $longCreateDate = 0;

    /**
     * The start-date of the date-table
     * @var class_date
     * @versionable
     * @templateExport
     * @templateMapper datetime
     *
     * @tableColumn system_date.system_date_start
     */
    private $objStartDate = null;

    /**
     * The end date of the date-table
     * @var class_date
     * @versionable
     * @templateExport
     * @templateMapper datetime
     *
     * @tableColumn system_date.system_date_end
     */
    private $objEndDate = null;

    /**
     * The special-date of the date-table
     * @var class_date
     * @versionable
     * @templateExport
     * @templateMapper datetime
     *
     * @tableColumn system_date.system_date_special
     */
    private $objSpecialDate = null;

    private $bitDatesChanges = false;


    /**
     * Constructor
     *
     * @param string $strSystemid
     *
     * @return class_root
     */
    public function __construct($strSystemid = "") {

        //Generating all the needed objects. For this we use our cool cool carrier-object
        //take care of loading just the necessary objects
        $objCarrier = class_carrier::getInstance();
        $this->objConfig = $objCarrier->getObjConfig();
        $this->objDB = $objCarrier->getObjDB();
        $this->objSession = $objCarrier->getObjSession();
        $this->objLang = $objCarrier->getObjLang();

        $this->objSortManager = new class_common_sortmanager($this);

        //And keep the action
        $this->strAction = $this->getParam("action");

        $this->strSystemid = $strSystemid;

        $this->setStrRecordClass(get_class($this));

        //try to load the current module-name and the moduleId by reflection
        $objReflection = new class_reflection($this);
        if(!isset($this->arrModule["modul"])) {
            $arrAnnotationValues = $objReflection->getAnnotationValuesFromClass(self::STR_MODULE_ANNOTATION);
            if(count($arrAnnotationValues) > 0) {
                $this->setArrModuleEntry("modul", trim($arrAnnotationValues[0]));
                $this->setArrModuleEntry("module", trim($arrAnnotationValues[0]));
            }
        }

        if(!isset($this->arrModule["moduleId"])) {
            $arrAnnotationValues = $objReflection->getAnnotationValuesFromClass(self::STR_MODULEID_ANNOTATION);
            if(count($arrAnnotationValues) > 0) {
                $this->setArrModuleEntry("moduleId", constant(trim($arrAnnotationValues[0])));
                $this->setIntModuleNr(constant(trim($arrAnnotationValues[0])));
            }
        }

        if($strSystemid != "") {
            $this->initObject();
        }
    }


    /**
     * Method to invoke object initialization.
     * In nearly all cases, this is triggered by the framework itself.
     * @return void
     */
    public final function initObject() {
        $this->initObjectInternal();
        $this->internalInit();

        //if given, read versioning information
        if($this instanceof interface_versionable) {
            $objChangelog = new class_module_system_changelog();
            $objChangelog->readOldValues($this);
        }

    }

    /**
     * InitObjectInternal is called during an objects instantiation.
     * The default implementation tries to map all database-fields to the objects fields
     * and sets the values automatically.
     *
     * If you have a different column-property mapping or additional
     * setters to call, overwrite this method.
     * The row loaded from the database is available by calling $this->getArrInitRow().
     * @return void
     */
    protected function initObjectInternal() {
        $objORM = new class_orm_objectinit($this);
        $objORM->initObjectFromDb();
    }

    /**
     * Init the current record with the system-fields
     * @return void
     */
    private final function internalInit() {

        if(validateSystemid($this->getSystemid())) {

            if(is_array($this->arrInitRow)) {
                $arrRow = $this->arrInitRow;
            }
            else {
                $strQuery = "SELECT *
                               FROM "._dbprefix_."system
                          LEFT JOIN "._dbprefix_."system_date
                                 ON system_id = system_date_id
                              WHERE system_id = ? ";
                $arrRow = $this->objDB->getPRow($strQuery, array($this->getSystemid()));
            }


            if(count($arrRow) > 3) {
                //$this->setStrSystemid($arrRow["system_id"]);
                $this->strPrevId =$arrRow["system_prev_id"];
                $this->intModuleNr = $arrRow["system_module_nr"];
                $this->intSort = $arrRow["system_sort"];
                $this->strOwner = $arrRow["system_owner"];
                $this->strLmUser = $arrRow["system_lm_user"];
                $this->intLmTime = $arrRow["system_lm_time"];
                $this->strLockId = $arrRow["system_lock_id"];
                $this->intLockTime = $arrRow["system_lock_time"];
                $this->intRecordStatus = $arrRow["system_status"];
                $this->strRecordComment = $arrRow["system_comment"];
                $this->longCreateDate = $arrRow["system_create_date"];
                if(isset($arrRow["system_class"]))
                    $this->strRecordClass = $arrRow["system_class"];

                if(isset($arrRow["system_deleted"]))
                    $this->intRecordDeleted = $arrRow["system_deleted"];

                $this->strOldPrevId = $this->strPrevId;

                if($arrRow["system_date_start"] > 0)
                    $this->objStartDate = new class_date($arrRow["system_date_start"]);

                if($arrRow["system_date_end"] > 0)
                    $this->objEndDate = new class_date($arrRow["system_date_end"]);

                if($arrRow["system_date_special"] > 0)
                    $this->objSpecialDate = new class_date($arrRow["system_date_special"]);

            }

            $this->bitDatesChanges = false;
        }
    }


    /**
     * A generic approach to count the number of object currently available.
     * This method is only a simple approach to determine the number of instances in the
     * database, if you need more specific counts, overwrite this method or add your own
     * implementation to the derived class.
     *
     * @param string $strPrevid
     *
     * @return int
     */
    public static function getObjectCount($strPrevid = "") {
        $objORM = new class_orm_objectlist();
        return $objORM->getObjectCount(get_called_class(), $strPrevid);
    }

    /**
     * A generic approach to load a list of objects currently available.
     * This method is only a simple approach to determine the instances in the
     * database, if you need more specific loaders, overwrite this method or add your own
     * implementation to the derived class.
     *
     * @param string $strPrevid
     * @param null|int $intStart
     * @param null|int $intEnd
     *
     * @return self[]
     */
    public static function getObjectList($strPrevid = "", $intStart = null, $intEnd = null) {
        $objORM = new class_orm_objectlist();
        return $objORM->getObjectList(get_called_class(), $strPrevid, $intStart, $intEnd);
    }


    /**
     * Validates if the current record may be restored
     * @return bool
     */
    public function isRestorable() {
        //validate the parent nodes' id
        $objParent = class_objectfactory::getInstance()->getObject($this->getStrPrevId());
        return $objParent != null && $objParent->getIntRecordDeleted() == 0;
    }


    public function restoreObject() {

        /** @var $this class_root|interface_model */
        $this->objDB->transactionBegin();

        $this->intRecordDeleted = 0;
        $this->intSort = $this->getNextSortValue($this->getStrPrevId());
        $bitReturn = $this->updateObjectToDb();

        class_objectfactory::getInstance()->removeFromCache($this->getSystemid());
        class_orm_rowcache::removeSingleRow($this->getSystemid());
        $this->objDB->flushQueryCache();

        $this->objSortManager->fixSortOnPrevIdChange($this->strPrevId, $this->strPrevId);

        $bitReturn = $bitReturn && class_core_eventdispatcher::getInstance()->notifyGenericListeners(class_system_eventidentifier::EVENT_SYSTEM_RECORDRESTORED_LOGICALLY, array($this->getSystemid(), get_class($this), $this));

        if($bitReturn) {
            class_logger::getInstance()->addLogRow("successfully restored record ".$this->getSystemid()." / ".$this->getStrDisplayName(), class_logger::$levelInfo);
            $this->objDB->transactionCommit();
            return true;
        }
        else {
            class_logger::getInstance()->addLogRow("error restoring record ".$this->getSystemid()." / ".$this->getStrDisplayName(), class_logger::$levelInfo);
            $this->objDB->transactionRollback();
            return false;
        }
    }


    /**
     * Triggers the logical delete of the current object.
     * This means the object itself is not deleted, but marked as deleted. Restoring the object is
     * possible.
     *
     * @throws class_exception
     * @return bool
     */
    public function deleteObject() {

        if(!$this->getLockManager()->isAccessibleForCurrentUser())
            return false;

        /** @var $this class_root|interface_model */
        $this->objDB->transactionBegin();

        //validate, if there are subrecords, so child nodes to be deleted
        $arrChilds = $this->objDB->getPArray("SELECT system_id FROM "._dbprefix_."system where system_prev_id = ?", array($this->getSystemid()));
        foreach($arrChilds as $arrOneChild) {
            if(validateSystemid($arrOneChild["system_id"])) {
                $objInstance = class_objectfactory::getInstance()->getObject($arrOneChild["system_id"]);
                if($objInstance !== null)
                    $objInstance->deleteObject();
            }
        }

        $this->intRecordDeleted = 1;
        $this->intSort = -1;
        $bitReturn = $this->updateObjectToDb();

        class_objectfactory::getInstance()->removeFromCache($this->getSystemid());
        class_orm_rowcache::removeSingleRow($this->getSystemid());
        $this->objDB->flushQueryCache();

        $this->objSortManager->fixSortOnDelete();

        $bitReturn = $bitReturn && class_core_eventdispatcher::getInstance()->notifyGenericListeners(class_system_eventidentifier::EVENT_SYSTEM_RECORDDELETED_LOGICALLY, array($this->getSystemid(), get_class($this)));

        if($bitReturn) {
            class_logger::getInstance()->addLogRow("successfully deleted record ".$this->getSystemid()." / ".$this->getStrDisplayName(), class_logger::$levelInfo);
            $this->objDB->transactionCommit();
            return true;
        }
        else {
            class_logger::getInstance()->addLogRow("error deleting record ".$this->getSystemid()." / ".$this->getStrDisplayName(), class_logger::$levelInfo);
            $this->objDB->transactionRollback();
            return false;
        }

    }

    /**
     * Deletes the object from the database. The record is removed in total, so no restoring will be possible.
     *
     * @return bool
     * @throws class_exception
     */
    public function deleteObjectFromDatabase() {
        if(!$this->getLockManager()->isAccessibleForCurrentUser())
            return false;

        if($this instanceof interface_versionable) {
            $objChanges = new class_module_system_changelog();
            $objChanges->createLogEntry($this, class_module_system_changelog::$STR_ACTION_DELETE);
        }

        /** @var $this class_root|interface_model */
        $this->objDB->transactionBegin();

        //validate, if there are subrecords, so child nodes to be deleted
        $arrChilds = $this->objDB->getPArray("SELECT system_id FROM "._dbprefix_."system where system_prev_id = ?", array($this->getSystemid()));
        foreach($arrChilds as $arrOneChild) {
            if(validateSystemid($arrOneChild["system_id"])) {
                $objInstance = class_objectfactory::getInstance()->getObject($arrOneChild["system_id"]);
                if($objInstance !== null)
                    $objInstance->deleteObjectFromDatabase();
            }
        }

        $objORM = new class_orm_objectdelete($this);
        $bitReturn = $objORM->deleteObject();

        $this->objSortManager->fixSortOnDelete();
        $bitReturn = $bitReturn && $this->deleteSystemRecord($this->getSystemid());

        class_objectfactory::getInstance()->removeFromCache($this->getSystemid());
        class_orm_rowcache::removeSingleRow($this->getSystemid());


        //try to call other modules, maybe wanting to delete anything in addition, if the current record
        //is going to be deleted
        $bitReturn = $bitReturn && class_core_eventdispatcher::getInstance()->notifyGenericListeners(class_system_eventidentifier::EVENT_SYSTEM_RECORDDELETED, array($this->getSystemid(), get_class($this)));

        if($bitReturn) {
            class_logger::getInstance()->addLogRow("successfully deleted record ".$this->getSystemid()." / ".$this->getStrDisplayName(), class_logger::$levelInfo);
            $this->objDB->transactionCommit();
            $this->objDB->flushQueryCache();
            return true;
        }
        else {
            class_logger::getInstance()->addLogRow("error deleting record ".$this->getSystemid()." / ".$this->getStrDisplayName(), class_logger::$levelInfo);
            $this->objDB->transactionRollback();
            $this->objDB->flushQueryCache();
            return false;
        }
    }

    // --- DATABASE-SYNCHRONIZATION -------------------------------------------------------------------------


    /**
     * Saves the current object to the database. Determines, whether the current object has to be inserted
     * or updated to the database.
     * In case of an update, the objects' updateStateToDb() method is being called (as required by class_model).
     * In the case of a new object, a blank record is being created. Therefore, all tables returned by class' doc comment
     * will be filled with a new record (using the same new systemid as the primary key).
     * The newly created systemid is being set as the current objects' one and can be used in the afterwards
     * called updateStateToDb() method to reference the correct rows.
     *
     * @param string|bool $strPrevId The prev-id of the records, either to be used for the insert or to be used during the update of the record
     * @return bool
     * @since 3.3.0
     * @throws class_exception
     * @see interface_model
     *
     * @todo move to class_orm_objectupdate completely
     */
    public function updateObjectToDb($strPrevId = false) {
        $bitCommit = true;
        /** @var $this class_root|interface_model */
        if(!$this instanceof interface_model)
            throw new class_exception("current object must implemented interface_model", class_exception::$level_FATALERROR);

        if(!$this->getLockManager()->isAccessibleForCurrentUser()) {
            $objUser  = new class_module_user_user($this->getLockManager()->getLockId());
            throw new class_exception("current object is locked by user ".$objUser->getStrDisplayName(), class_exception::$level_ERROR);
        }

        if(is_object($strPrevId) && $strPrevId instanceof class_root)
            $strPrevId = $strPrevId->getSystemid();

        $this->objDB->transactionBegin();

        //current systemid given? if not, create a new record.
        if(!validateSystemid($this->getSystemid())) {

            if($strPrevId === false || $strPrevId === "" || $strPrevId === null) {
                //try to find the current modules-one
                if(isset($this->arrModule["modul"])) {
                    $strPrevId = class_module_system_module::getModuleByName($this->getArrModule("modul"), true)->getSystemid();
                    if(!validateSystemid($strPrevId))
                        throw new class_exception("automatic determination of module-id failed ", class_exception::$level_FATALERROR);
                }
                else
                    throw new class_exception("insert with no previd ", class_exception::$level_FATALERROR);
            }

            if(!validateSystemid($strPrevId) && $strPrevId !== "0") {
                throw new class_exception("insert with erroneous prev-id ", class_exception::$level_FATALERROR);
            }

            //create the new systemrecord
            //store date-bit temporary
            $bitDates = $this->bitDatesChanges;
            $this->createSystemRecord($strPrevId, $this->getStrDisplayName());
            $this->bitDatesChanges = $bitDates;

            if(validateSystemid($this->getStrSystemid())) {

                //Create the foreign records
                $objAnnotations = new class_reflection($this);
                $arrTargetTables = $objAnnotations->getAnnotationValuesFromClass("@targetTable");
                if(count($arrTargetTables) > 0) {
                    foreach($arrTargetTables as $strOneConfig) {
                        $arrSingleTable = explode(".", $strOneConfig);
                        $strQuery = "INSERT INTO ".$this->objDB->encloseTableName(_dbprefix_.$arrSingleTable[0])."
                                                (".$this->objDB->encloseColumnName($arrSingleTable[1]).") VALUES
                                                (?) ";

                        if(!$this->objDB->_pQuery($strQuery, array($this->getStrSystemid())))
                            $bitCommit = false;
                    }
                }

                if(!$this->onInsertToDb())
                    $bitCommit = false;

            }
            else
                throw new class_exception("creation of systemrecord failed", class_exception::$level_FATALERROR);

            //all updates are done, start the "real" update
            class_carrier::getInstance()->flushCache(class_carrier::INT_CACHE_TYPE_DBQUERIES);
        }

        //new prev-id?
        if($strPrevId !== false && $this->getSystemid() != $strPrevId && (validateSystemid($strPrevId) || $strPrevId == "0")) {
            //validate the new prev id - it is not allowed to set a parent-node as a sub-node of its own child
            if(!$this->isSystemidChildNode($this->getSystemid(), $strPrevId))
                $this->setStrPrevId($strPrevId);
        }

        //new comment?
        $this->setStrRecordComment($this->getStrDisplayName());

        //save back to the database
        $bitCommit = $bitCommit && $this->updateSystemrecord();

        //update ourselves to the database
        if($bitCommit && !$this->updateStateToDb())
            $bitCommit = false;

        if($bitCommit) {
            $this->objDB->transactionCommit();
            //unlock the record
            $this->getLockManager()->unlockRecord();
            class_logger::getInstance()->addLogRow("updateObjectToDb() succeeded for systemid ".$this->getSystemid()." (".$this->getRecordComment().")", class_logger::$levelInfo);
        }
        else {
            $this->objDB->transactionRollback();
            class_logger::getInstance()->addLogRow("updateObjectToDb() failed for systemid ".$this->getSystemid()." (".$this->getRecordComment().")", class_logger::$levelWarning);
        }


        //call the recordUpdated-Listeners
        class_core_eventdispatcher::getInstance()->notifyGenericListeners(class_system_eventidentifier::EVENT_SYSTEM_RECORDUPDATED, array($this));

        class_carrier::getInstance()->flushCache(class_carrier::INT_CACHE_TYPE_DBQUERIES);
        return $bitCommit;
    }


    /**
     * A default implementation for copy-operations.
     * Overwrite this method if you want to execute additional statements.<commit_msg>NEW: module_system | class_root updateObjectToDb -> recordupdate listener now has an additional parameter if the object was created or not<commit_after><?php
/*"******************************************************************************************************
*   (c) 2004-2006 by MulchProductions, www.mulchprod.de                                                 *
*   (c) 2007-2015 by Kajona, www.kajona.de                                                              *
*       Published under the GNU LGPL v2.1, see /system/licence_lgpl.txt                                 *
*-------------------------------------------------------------------------------------------------------*
*   $Id$                                              *
********************************************************************************************************/

/**
 * The top-level class for models, installers and top-level files
 * An instance of this class is used by the admin & portal object to invoke common database based methods.
 * Change with care!
 *
 * @package module_system
 * @author <EMAIL>
 */
abstract class class_root {

    const STR_MODULE_ANNOTATION = "@module";
    const STR_MODULEID_ANNOTATION = "@moduleId";


    /**
     * Instance of class_config
     *
     * @var class_config
     */
    protected $objConfig = null; //Object containing config-data
    /**
     * Instance of class_db
     *
     * @var class_db
     */
    protected $objDB = null; //Object to the database
    /**
     * Instance of class_session
     *
     * @var class_session
     */
    protected $objSession = null; //Object containing the session-management

    /**
     * Instance of class_lang
     *
     * @var class_lang
     */
    private $objLang = null; //Object managing the langfiles

    /**
     * @var interface_sortmanager
     */
    protected $objSortManager = null;

    private $strAction; //current action to perform (GET/POST)
    protected $arrModule = array(); //Array containing information about the current module


    private $arrInitRow = null;             //array to be used when loading details from the database. could reduce the amount of queries if populated.

    //--- fields to be synchronized with the database ---

    /**
     * The records current systemid
     * @var string
     * @templateExport
     *
     * @tableColumn system.system_id
     */
    private $strSystemid = "";

    /**
     * The records internal parent-id
     * @var string
     * @versionable
     *
     * @tableColumn system.system_prev_id
     */
    private $strPrevId = -1;

    /**
     * The old prev-id, used to track hierarchical changes -> requires a rebuild of the rights-table
     * @var string
     */
    private $strOldPrevId = -1;

    /**
     * The records module-number
     * @var int
     * @tableColumn system.system_module_nr
     */
    private $intModuleNr = 0;

    /**
     * The records sort-position relative to the parent record
     * @var int
     * @tableColumn system.system_sort
     */
    private $intSort = -1;

    /**
     * The id of the user who created the record initially
     * @var string
     * @versionable
     * @templateExport
     * @templateMapper user
     * @tableColumn system.system_owner
     */
    private $strOwner = "";

    /**
     * The id of the user last who did the last changes to the current record
     * @var string
     * @tableColumn system.system_lm_user
     */
    private $strLmUser = "";

    /**
     * Timestamp of the last modification
     * ATTENTION: time() based, so 32 bit integer
     * @todo migrate to long-timestamp
     * @var int
     * @templateExport
     * @templateMapper datetime
     * @tableColumn system.system_lm_time
     */
    private $intLmTime = 0;

    /**
     * The id of the user locking the current record, emtpy otherwise
     * @var string
     * @tableColumn system.system_lock_id
     */
    private $strLockId = "";

    /**
     * Time the current locking was triggered
     * ATTENTION: time() based, so 32 bit integer
     * @todo migrate to long-timestamp
     * @var int
     * @tableColumn system.system_lock_time
     */
    private $intLockTime = 0;

    /**
     * The records status
     * @var int
     * @versionable
     * @tableColumn system.system_status
     */
    private $intRecordStatus = 1;

    /**
     * Indicates whether the object is deleted, or not
     * @var int
     * @versionable
     * @tableColumn system.system_deleted
     */
    private $intRecordDeleted = 0;

    /**
     * Human readable comment describing the current record
     * @var string
     * @tableColumn system.system_comment
     */
    private $strRecordComment = "";

    /**
     * Holds the current objects' class
     * @var string
     * @tableColumn system.system_class
     */
    private $strRecordClass = "";

    /**
     * Long-based representation of the timestamp the record was created initially
     * @var int
     * @templateExport
     * @templateMapper datetime
     * @tableColumn system.system_create_date
     */
    private $longCreateDate = 0;

    /**
     * The start-date of the date-table
     * @var class_date
     * @versionable
     * @templateExport
     * @templateMapper datetime
     *
     * @tableColumn system_date.system_date_start
     */
    private $objStartDate = null;

    /**
     * The end date of the date-table
     * @var class_date
     * @versionable
     * @templateExport
     * @templateMapper datetime
     *
     * @tableColumn system_date.system_date_end
     */
    private $objEndDate = null;

    /**
     * The special-date of the date-table
     * @var class_date
     * @versionable
     * @templateExport
     * @templateMapper datetime
     *
     * @tableColumn system_date.system_date_special
     */
    private $objSpecialDate = null;

    private $bitDatesChanges = false;


    /**
     * Constructor
     *
     * @param string $strSystemid
     *
     * @return class_root
     */
    public function __construct($strSystemid = "") {

        //Generating all the needed objects. For this we use our cool cool carrier-object
        //take care of loading just the necessary objects
        $objCarrier = class_carrier::getInstance();
        $this->objConfig = $objCarrier->getObjConfig();
        $this->objDB = $objCarrier->getObjDB();
        $this->objSession = $objCarrier->getObjSession();
        $this->objLang = $objCarrier->getObjLang();

        $this->objSortManager = new class_common_sortmanager($this);

        //And keep the action
        $this->strAction = $this->getParam("action");

        $this->strSystemid = $strSystemid;

        $this->setStrRecordClass(get_class($this));

        //try to load the current module-name and the moduleId by reflection
        $objReflection = new class_reflection($this);
        if(!isset($this->arrModule["modul"])) {
            $arrAnnotationValues = $objReflection->getAnnotationValuesFromClass(self::STR_MODULE_ANNOTATION);
            if(count($arrAnnotationValues) > 0) {
                $this->setArrModuleEntry("modul", trim($arrAnnotationValues[0]));
                $this->setArrModuleEntry("module", trim($arrAnnotationValues[0]));
            }
        }

        if(!isset($this->arrModule["moduleId"])) {
            $arrAnnotationValues = $objReflection->getAnnotationValuesFromClass(self::STR_MODULEID_ANNOTATION);
            if(count($arrAnnotationValues) > 0) {
                $this->setArrModuleEntry("moduleId", constant(trim($arrAnnotationValues[0])));
                $this->setIntModuleNr(constant(trim($arrAnnotationValues[0])));
            }
        }

        if($strSystemid != "") {
            $this->initObject();
        }
    }


    /**
     * Method to invoke object initialization.
     * In nearly all cases, this is triggered by the framework itself.
     * @return void
     */
    public final function initObject() {
        $this->initObjectInternal();
        $this->internalInit();

        //if given, read versioning information
        if($this instanceof interface_versionable) {
            $objChangelog = new class_module_system_changelog();
            $objChangelog->readOldValues($this);
        }

    }

    /**
     * InitObjectInternal is called during an objects instantiation.
     * The default implementation tries to map all database-fields to the objects fields
     * and sets the values automatically.
     *
     * If you have a different column-property mapping or additional
     * setters to call, overwrite this method.
     * The row loaded from the database is available by calling $this->getArrInitRow().
     * @return void
     */
    protected function initObjectInternal() {
        $objORM = new class_orm_objectinit($this);
        $objORM->initObjectFromDb();
    }

    /**
     * Init the current record with the system-fields
     * @return void
     */
    private final function internalInit() {

        if(validateSystemid($this->getSystemid())) {

            if(is_array($this->arrInitRow)) {
                $arrRow = $this->arrInitRow;
            }
            else {
                $strQuery = "SELECT *
                               FROM "._dbprefix_."system
                          LEFT JOIN "._dbprefix_."system_date
                                 ON system_id = system_date_id
                              WHERE system_id = ? ";
                $arrRow = $this->objDB->getPRow($strQuery, array($this->getSystemid()));
            }


            if(count($arrRow) > 3) {
                //$this->setStrSystemid($arrRow["system_id"]);
                $this->strPrevId =$arrRow["system_prev_id"];
                $this->intModuleNr = $arrRow["system_module_nr"];
                $this->intSort = $arrRow["system_sort"];
                $this->strOwner = $arrRow["system_owner"];
                $this->strLmUser = $arrRow["system_lm_user"];
                $this->intLmTime = $arrRow["system_lm_time"];
                $this->strLockId = $arrRow["system_lock_id"];
                $this->intLockTime = $arrRow["system_lock_time"];
                $this->intRecordStatus = $arrRow["system_status"];
                $this->strRecordComment = $arrRow["system_comment"];
                $this->longCreateDate = $arrRow["system_create_date"];
                if(isset($arrRow["system_class"]))
                    $this->strRecordClass = $arrRow["system_class"];

                if(isset($arrRow["system_deleted"]))
                    $this->intRecordDeleted = $arrRow["system_deleted"];

                $this->strOldPrevId = $this->strPrevId;

                if($arrRow["system_date_start"] > 0)
                    $this->objStartDate = new class_date($arrRow["system_date_start"]);

                if($arrRow["system_date_end"] > 0)
                    $this->objEndDate = new class_date($arrRow["system_date_end"]);

                if($arrRow["system_date_special"] > 0)
                    $this->objSpecialDate = new class_date($arrRow["system_date_special"]);

            }

            $this->bitDatesChanges = false;
        }
    }


    /**
     * A generic approach to count the number of object currently available.
     * This method is only a simple approach to determine the number of instances in the
     * database, if you need more specific counts, overwrite this method or add your own
     * implementation to the derived class.
     *
     * @param string $strPrevid
     *
     * @return int
     */
    public static function getObjectCount($strPrevid = "") {
        $objORM = new class_orm_objectlist();
        return $objORM->getObjectCount(get_called_class(), $strPrevid);
    }

    /**
     * A generic approach to load a list of objects currently available.
     * This method is only a simple approach to determine the instances in the
     * database, if you need more specific loaders, overwrite this method or add your own
     * implementation to the derived class.
     *
     * @param string $strPrevid
     * @param null|int $intStart
     * @param null|int $intEnd
     *
     * @return self[]
     */
    public static function getObjectList($strPrevid = "", $intStart = null, $intEnd = null) {
        $objORM = new class_orm_objectlist();
        return $objORM->getObjectList(get_called_class(), $strPrevid, $intStart, $intEnd);
    }


    /**
     * Validates if the current record may be restored
     * @return bool
     */
    public function isRestorable() {
        //validate the parent nodes' id
        $objParent = class_objectfactory::getInstance()->getObject($this->getStrPrevId());
        return $objParent != null && $objParent->getIntRecordDeleted() == 0;
    }


    public function restoreObject() {

        /** @var $this class_root|interface_model */
        $this->objDB->transactionBegin();

        $this->intRecordDeleted = 0;
        $this->intSort = $this->getNextSortValue($this->getStrPrevId());
        $bitReturn = $this->updateObjectToDb();

        class_objectfactory::getInstance()->removeFromCache($this->getSystemid());
        class_orm_rowcache::removeSingleRow($this->getSystemid());
        $this->objDB->flushQueryCache();

        $this->objSortManager->fixSortOnPrevIdChange($this->strPrevId, $this->strPrevId);

        $bitReturn = $bitReturn && class_core_eventdispatcher::getInstance()->notifyGenericListeners(class_system_eventidentifier::EVENT_SYSTEM_RECORDRESTORED_LOGICALLY, array($this->getSystemid(), get_class($this), $this));

        if($bitReturn) {
            class_logger::getInstance()->addLogRow("successfully restored record ".$this->getSystemid()." / ".$this->getStrDisplayName(), class_logger::$levelInfo);
            $this->objDB->transactionCommit();
            return true;
        }
        else {
            class_logger::getInstance()->addLogRow("error restoring record ".$this->getSystemid()." / ".$this->getStrDisplayName(), class_logger::$levelInfo);
            $this->objDB->transactionRollback();
            return false;
        }
    }


    /**
     * Triggers the logical delete of the current object.
     * This means the object itself is not deleted, but marked as deleted. Restoring the object is
     * possible.
     *
     * @throws class_exception
     * @return bool
     */
    public function deleteObject() {

        if(!$this->getLockManager()->isAccessibleForCurrentUser())
            return false;

        /** @var $this class_root|interface_model */
        $this->objDB->transactionBegin();

        //validate, if there are subrecords, so child nodes to be deleted
        $arrChilds = $this->objDB->getPArray("SELECT system_id FROM "._dbprefix_."system where system_prev_id = ?", array($this->getSystemid()));
        foreach($arrChilds as $arrOneChild) {
            if(validateSystemid($arrOneChild["system_id"])) {
                $objInstance = class_objectfactory::getInstance()->getObject($arrOneChild["system_id"]);
                if($objInstance !== null)
                    $objInstance->deleteObject();
            }
        }

        $this->intRecordDeleted = 1;
        $this->intSort = -1;
        $bitReturn = $this->updateObjectToDb();

        class_objectfactory::getInstance()->removeFromCache($this->getSystemid());
        class_orm_rowcache::removeSingleRow($this->getSystemid());
        $this->objDB->flushQueryCache();

        $this->objSortManager->fixSortOnDelete();

        $bitReturn = $bitReturn && class_core_eventdispatcher::getInstance()->notifyGenericListeners(class_system_eventidentifier::EVENT_SYSTEM_RECORDDELETED_LOGICALLY, array($this->getSystemid(), get_class($this)));

        if($bitReturn) {
            class_logger::getInstance()->addLogRow("successfully deleted record ".$this->getSystemid()." / ".$this->getStrDisplayName(), class_logger::$levelInfo);
            $this->objDB->transactionCommit();
            return true;
        }
        else {
            class_logger::getInstance()->addLogRow("error deleting record ".$this->getSystemid()." / ".$this->getStrDisplayName(), class_logger::$levelInfo);
            $this->objDB->transactionRollback();
            return false;
        }

    }

    /**
     * Deletes the object from the database. The record is removed in total, so no restoring will be possible.
     *
     * @return bool
     * @throws class_exception
     */
    public function deleteObjectFromDatabase() {
        if(!$this->getLockManager()->isAccessibleForCurrentUser())
            return false;

        if($this instanceof interface_versionable) {
            $objChanges = new class_module_system_changelog();
            $objChanges->createLogEntry($this, class_module_system_changelog::$STR_ACTION_DELETE);
        }

        /** @var $this class_root|interface_model */
        $this->objDB->transactionBegin();

        //validate, if there are subrecords, so child nodes to be deleted
        $arrChilds = $this->objDB->getPArray("SELECT system_id FROM "._dbprefix_."system where system_prev_id = ?", array($this->getSystemid()));
        foreach($arrChilds as $arrOneChild) {
            if(validateSystemid($arrOneChild["system_id"])) {
                $objInstance = class_objectfactory::getInstance()->getObject($arrOneChild["system_id"]);
                if($objInstance !== null)
                    $objInstance->deleteObjectFromDatabase();
            }
        }

        $objORM = new class_orm_objectdelete($this);
        $bitReturn = $objORM->deleteObject();

        $this->objSortManager->fixSortOnDelete();
        $bitReturn = $bitReturn && $this->deleteSystemRecord($this->getSystemid());

        class_objectfactory::getInstance()->removeFromCache($this->getSystemid());
        class_orm_rowcache::removeSingleRow($this->getSystemid());


        //try to call other modules, maybe wanting to delete anything in addition, if the current record
        //is going to be deleted
        $bitReturn = $bitReturn && class_core_eventdispatcher::getInstance()->notifyGenericListeners(class_system_eventidentifier::EVENT_SYSTEM_RECORDDELETED, array($this->getSystemid(), get_class($this)));

        if($bitReturn) {
            class_logger::getInstance()->addLogRow("successfully deleted record ".$this->getSystemid()." / ".$this->getStrDisplayName(), class_logger::$levelInfo);
            $this->objDB->transactionCommit();
            $this->objDB->flushQueryCache();
            return true;
        }
        else {
            class_logger::getInstance()->addLogRow("error deleting record ".$this->getSystemid()." / ".$this->getStrDisplayName(), class_logger::$levelInfo);
            $this->objDB->transactionRollback();
            $this->objDB->flushQueryCache();
            return false;
        }
    }

    // --- DATABASE-SYNCHRONIZATION -------------------------------------------------------------------------


    /**
     * Saves the current object to the database. Determines, whether the current object has to be inserted
     * or updated to the database.
     * In case of an update, the objects' updateStateToDb() method is being called (as required by class_model).
     * In the case of a new object, a blank record is being created. Therefore, all tables returned by class' doc comment
     * will be filled with a new record (using the same new systemid as the primary key).
     * The newly created systemid is being set as the current objects' one and can be used in the afterwards
     * called updateStateToDb() method to reference the correct rows.
     *
     * @param string|bool $strPrevId The prev-id of the records, either to be used for the insert or to be used during the update of the record
     * @return bool
     * @since 3.3.0
     * @throws class_exception
     * @see interface_model
     *
     * @todo move to class_orm_objectupdate completely
     */
    public function updateObjectToDb($strPrevId = false) {
        $bitCommit = true;
        /** @var $this class_root|interface_model */
        if(!$this instanceof interface_model)
            throw new class_exception("current object must implemented interface_model", class_exception::$level_FATALERROR);

        if(!$this->getLockManager()->isAccessibleForCurrentUser()) {
            $objUser  = new class_module_user_user($this->getLockManager()->getLockId());
            throw new class_exception("current object is locked by user ".$objUser->getStrDisplayName(), class_exception::$level_ERROR);
        }

        if(is_object($strPrevId) && $strPrevId instanceof class_root)
            $strPrevId = $strPrevId->getSystemid();

        $this->objDB->transactionBegin();

        //current systemid given? if not, create a new record.
        $bitRecordCreated = false;
        if(!validateSystemid($this->getSystemid())) {
            $bitRecordCreated = true;

            if($strPrevId === false || $strPrevId === "" || $strPrevId === null) {
                //try to find the current modules-one
                if(isset($this->arrModule["modul"])) {
                    $strPrevId = class_module_system_module::getModuleByName($this->getArrModule("modul"), true)->getSystemid();
                    if(!validateSystemid($strPrevId))
                        throw new class_exception("automatic determination of module-id failed ", class_exception::$level_FATALERROR);
                }
                else
                    throw new class_exception("insert with no previd ", class_exception::$level_FATALERROR);
            }

            if(!validateSystemid($strPrevId) && $strPrevId !== "0") {
                throw new class_exception("insert with erroneous prev-id ", class_exception::$level_FATALERROR);
            }

            //create the new systemrecord
            //store date-bit temporary
            $bitDates = $this->bitDatesChanges;
            $this->createSystemRecord($strPrevId, $this->getStrDisplayName());
            $this->bitDatesChanges = $bitDates;

            if(validateSystemid($this->getStrSystemid())) {

                //Create the foreign records
                $objAnnotations = new class_reflection($this);
                $arrTargetTables = $objAnnotations->getAnnotationValuesFromClass("@targetTable");
                if(count($arrTargetTables) > 0) {
                    foreach($arrTargetTables as $strOneConfig) {
                        $arrSingleTable = explode(".", $strOneConfig);
                        $strQuery = "INSERT INTO ".$this->objDB->encloseTableName(_dbprefix_.$arrSingleTable[0])."
                                                (".$this->objDB->encloseColumnName($arrSingleTable[1]).") VALUES
                                                (?) ";

                        if(!$this->objDB->_pQuery($strQuery, array($this->getStrSystemid())))
                            $bitCommit = false;
                    }
                }

                if(!$this->onInsertToDb())
                    $bitCommit = false;

            }
            else
                throw new class_exception("creation of systemrecord failed", class_exception::$level_FATALERROR);

            //all updates are done, start the "real" update
            class_carrier::getInstance()->flushCache(class_carrier::INT_CACHE_TYPE_DBQUERIES);
        }

        //new prev-id?
        if($strPrevId !== false && $this->getSystemid() != $strPrevId && (validateSystemid($strPrevId) || $strPrevId == "0")) {
            //validate the new prev id - it is not allowed to set a parent-node as a sub-node of its own child
            if(!$this->isSystemidChildNode($this->getSystemid(), $strPrevId))
                $this->setStrPrevId($strPrevId);
        }

        //new comment?
        $this->setStrRecordComment($this->getStrDisplayName());

        //save back to the database
        $bitCommit = $bitCommit && $this->updateSystemrecord();

        //update ourselves to the database
        if($bitCommit && !$this->updateStateToDb())
            $bitCommit = false;

        if($bitCommit) {
            $this->objDB->transactionCommit();
            //unlock the record
            $this->getLockManager()->unlockRecord();
            class_logger::getInstance()->addLogRow("updateObjectToDb() succeeded for systemid ".$this->getSystemid()." (".$this->getRecordComment().")", class_logger::$levelInfo);
        }
        else {
            $this->objDB->transactionRollback();
            class_logger::getInstance()->addLogRow("updateObjectToDb() failed for systemid ".$this->getSystemid()." (".$this->getRecordComment().")", class_logger::$levelWarning);
        }


        //call the recordUpdated-Listeners
        class_core_eventdispatcher::getInstance()->notifyGenericListeners(class_system_eventidentifier::EVENT_SYSTEM_RECORDUPDATED, array($this, $bitRecordCreated));

        class_carrier::getInstance()->flushCache(class_carrier::INT_CACHE_TYPE_DBQUERIES);
        return $bitCommit;
    }


    /**
     * A default implementation for copy-operations.
     * Overwrite this method if you want to execute additional statements.
{'fraction_non_alphanumeric': 0.13162588428625824, 'fraction_numerical': 0.002124291067957274, 'mean_word_length': 2.341816233592285, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 422, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6420504', 'n_tokens_mistral': 13417, 'n_tokens_neox': 12998, 'n_words': 3101}