diff --git a/.env b/.env index e84ed8e8..417b94b3 100644 --- a/.env +++ b/.env @@ -24,7 +24,7 @@ APP_SECRET=7915fd8481d1a52cf42010ebe2caa974 # Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url # For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db" # Configure your db driver and server_version in config/packages/doctrine.yaml -DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name +DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db ###< doctrine/doctrine-bundle ### ###> symfony/swiftmailer-bundle ### diff --git a/src/Controller/HomepageController.php b/src/Controller/HomepageController.php new file mode 100644 index 00000000..c0fb4f5f --- /dev/null +++ b/src/Controller/HomepageController.php @@ -0,0 +1,30 @@ +getDoctrine()->getRepository(Category::class); + + /** @var StructuralDBElement $attachment */ + $attachment = $repo->find(1); + + dump($attachment, $attachment->getSubelements(false)->toArray()); + $response = ""; + return $this->render('base.html.twig'); + } +} \ No newline at end of file diff --git a/src/Entity/Attachment.php b/src/Entity/Attachment.php new file mode 100644 index 00000000..089311ea --- /dev/null +++ b/src/Entity/Attachment.php @@ -0,0 +1,148 @@ +getFilename(), PATHINFO_EXTENSION); + + // list all file extensions which are supported to display them by HTML code + $picture_extensions = array('gif', 'png', 'jpg', 'jpeg', 'bmp', 'svg', 'tif'); + + return in_array(strtolower($extension), $picture_extensions, true); + } + + /******************************************************************************** + * + * Getters + * + *********************************************************************************/ + + /** + * Get the element, associated with this Attachement (for example a "Part" object) + * @return DBElement The associated Element. + */ + public function getElement() : AttachmentContainingDBElement + { + return $this->element; + } + + /** + * Checks if the file in this attachement is existing. This works for files on the HDD, and for URLs + * (it's not checked if the ressource behind the URL is really existing). + * + * @return bool True if the file is existing. + */ + public function isFileExisting() : bool + { + return file_exists($this->getFilename()) || isURL($this->getFilename()); + } + + /** + * Get the filename (absolute path from filesystem root, as a UNIX path [only slashes]) + * + * @return string the filename as an absolute UNIX filepath from filesystem root + */ + public function getFilename() : string + { + return str_replace('%BASE%', BASE, $this->filename); + } + + /** + * Get the show_in_table attribute + * + * @return bool true means, this attachement will be listed in the "Attachements" column of the HTML tables + * false means, this attachement won't be listed in the "Attachements" column of the HTML tables + */ + public function getShowInTable() : bool + { + return (bool) $this->show_in_table; + } + + /** + * Get the type of this attachement + * @return AttachmentType the type of this attachement + * @throws Exception if there was an error + */ + public function getType() : AttachmentType + { + //TODO + } + + /** + * Returns the ID as an string, defined by the element class. + * This should have a form like P000014, for a part with ID 14. + * @return string The ID as a string; + */ + public function getIDString(): string + { + return 'A' . sprintf('%09d', $this->getID()); + } +} \ No newline at end of file diff --git a/src/Entity/AttachmentContainingDBElement.php b/src/Entity/AttachmentContainingDBElement.php new file mode 100644 index 00000000..59faa071 --- /dev/null +++ b/src/Entity/AttachmentContainingDBElement.php @@ -0,0 +1,91 @@ +attachmentTypes; + } + + + /** + * Get all attachements of this element / Get the element's attachements with a specific type + * + * @param integer $type_id @li if NULL, all attachements of this element will be returned + * @li if this is a number > 0, only attachements with this type ID will be returned + * @param boolean $only_table_attachements if true, only attachements with "show_in_table == true" + * + * @return Attachment[] the attachements as a one-dimensional array of Attachement objects + * + * @throws Exception if there was an error + */ + public function getAttachments($type_id = null, bool $only_table_attachements = false) : array + { + if ($only_table_attachements || $type_id) { + $attachements = $this->attachments; + + foreach ($attachements as $key => $attachement) { + if (($only_table_attachements && (! $attachement->getShowInTable())) + || ($type_id && ($attachement->getType()->getID() != $type_id))) { + unset($attachements[$key]); + } + } + + return $attachements; + } else { + return $this->attachments; + } + } +} diff --git a/src/Entity/AttachmentType.php b/src/Entity/AttachmentType.php new file mode 100644 index 00000000..5f317e60 --- /dev/null +++ b/src/Entity/AttachmentType.php @@ -0,0 +1,82 @@ +attachements is used from class "AttachementsContainingDBELement" + if ($this->attachments == null) { + $this->attachments = new ArrayCollection(); + } + + return $this->attachments; + } + + /** + * Returns the ID as an string, defined by the element class. + * This should have a form like P000014, for a part with ID 14. + * @return string The ID as a string; + */ + public function getIDString(): string + { + return ""; + //return 'AT' . sprintf('%09d', $this->getID()); + } +} diff --git a/src/Entity/Category.php b/src/Entity/Category.php new file mode 100644 index 00000000..1ae4935c --- /dev/null +++ b/src/Entity/Category.php @@ -0,0 +1,110 @@ +getID()); + } +} diff --git a/src/Entity/Company.php b/src/Entity/Company.php new file mode 100644 index 00000000..5f8adf34 --- /dev/null +++ b/src/Entity/Company.php @@ -0,0 +1,211 @@ +address; + } + + /** + * Get the phone number + * + * @return string the phone number of the company + */ + public function getPhoneNumber() : string + { + return $this->phone_number; + } + + /** + * Get the fax number + * + * @return string the fax number of the company + */ + public function getFaxNumber() : string + { + return $this->fax_number; + } + + /** + * Get the e-mail address + * + * @return string the e-mail address of the company + */ + public function getEmailAddress() : string + { + return $this->email_address; + } + + /** + * Get the website + * + * @return string the website of the company + */ + public function getWebsite() : string + { + return $this->website; + } + + /** + * Get the link to the website of an article + * + * @param string $partnr @li NULL for returning the URL with a placeholder for the part number + * @li or the part number for returning the direct URL to the article + * + * @return string the link to the article + */ + public function getAutoProductUrl($partnr = null) : string + { + if (\is_string($partnr)) { + return str_replace('%PARTNUMBER%', $partnr, $this->auto_product_url); + } + return $this->auto_product_url; + } + + /******************************************************************************** + * + * Setters + * + *********************************************************************************/ + + /** + * Set the addres + * @param string $new_address the new address (with "\n" as line break) + */ + public function setAddress(string $new_address) + { + $this->address = $new_address; + } + + /** + * Set the phone number + * + * @param string $new_phone_number the new phone number + */ + public function setPhoneNumber(string $new_phone_number) + { + $this->phone_number = $new_phone_number; + } + + /** + * Set the fax number + * + * @param string $new_fax_number the new fax number + */ + public function setFaxNumber(string $new_fax_number) + { + $this->fax_number = $new_fax_number; + } + + /** + * Set the e-mail address + * + * @param string $new_email_address the new e-mail address + + */ + public function setEmailAddress(string $new_email_address) + { + $this->email_address = $new_email_address; + } + + /** + * Set the website + * + * @param string $new_website the new website + */ + public function setWebsite(string $new_website) + { + $this->website = $new_website; + } + + /** + * Set the link to the website of an article + * + * @param string $new_url the new URL with the placeholder %PARTNUMBER% for the part number + * + */ + public function setAutoProductUrl(string $new_url) + { + $this->auto_product_url = $new_url; + } + +} \ No newline at end of file diff --git a/src/Entity/DBElement.php b/src/Entity/DBElement.php new file mode 100644 index 00000000..37815dd4 --- /dev/null +++ b/src/Entity/DBElement.php @@ -0,0 +1,66 @@ +id; + } + + /** + * Returns the ID as an string, defined by the element class. + * This should have a form like P000014, for a part with ID 14. + * @return string The ID as a string; + */ + abstract public function getIDString() : string; + +} \ No newline at end of file diff --git a/src/Entity/Device.php b/src/Entity/Device.php new file mode 100644 index 00000000..ed96ff0e --- /dev/null +++ b/src/Entity/Device.php @@ -0,0 +1,111 @@ +order_quantity; + } + + /** + * Get the "order_only_missing_parts" attribute + * + * @return boolean the "order_only_missing_parts" attribute + */ + public function getOrderOnlyMissingParts() : bool + { + return $this->order_only_missing_parts; + } + + /******************************************************************************** + * + * Setters + * + *********************************************************************************/ + + /** + * Set the order quantity + * + * @param integer $new_order_quantity the new order quantity + */ + public function setOrderQuantity(int $new_order_quantity) + { + if($new_order_quantity < 0) + { + throw new \InvalidArgumentException("The new order quantity must not be negative!"); + } + $this->order_quantity = $new_order_quantity; + } + + /** + * Set the "order_only_missing_parts" attribute + * + * @param boolean $new_order_only_missing_parts the new "order_only_missing_parts" attribute + * + */ + public function setOrderOnlyMissingParts(bool $new_order_only_missing_parts) + { + $this->order_only_missing_parts = $new_order_only_missing_parts; + } + + + /** + * Returns the ID as an string, defined by the element class. + * This should have a form like P000014, for a part with ID 14. + * @return string The ID as a string; + */ + public function getIDString(): string + { + return 'D' . sprintf('%09d', $this->getID()); + } +} \ No newline at end of file diff --git a/src/Entity/DevicePart.php b/src/Entity/DevicePart.php new file mode 100644 index 00000000..e58180d2 --- /dev/null +++ b/src/Entity/DevicePart.php @@ -0,0 +1,42 @@ +getID()); + } +} \ No newline at end of file diff --git a/src/Entity/Footprint.php b/src/Entity/Footprint.php new file mode 100644 index 00000000..892e5fa5 --- /dev/null +++ b/src/Entity/Footprint.php @@ -0,0 +1,192 @@ +getID()); + } + + /**************************************** + * Getters + ****************************************/ + + /** + * Get the filename of the picture (absolute path from filesystem root) + * + * @param bool $absolute If set to true, then the absolute filename (from system root) is returned. + * If set to false, then the path relative to Part-DB folder is returned. + * @return string @li the absolute path to the picture (from filesystem root), as a UNIX path (with slashes) + * @li an empty string if there is no picture + */ + public function getFilename(bool $absolute = true) : string + { + if ($absolute == true) { + //TODO + throw new \Exception("Not Implemented yet..."); + //return str_replace('%BASE%', BASE, $this->db_data['filename']); + } else { + return $this->filename; + } + } + + /** + * Get the filename of the 3d model (absolute path from filesystem root) + * + * @param bool $absolute If set to true, then the absolute filename (from system root) is returned. + * If set to false, then the path relative to Part-DB folder is returned. + * + * @return string @li the absolute path to the model (from filesystem root), as a UNIX path (with slashes) + * @li an empty string if there is no model + */ + public function get3dFilename(bool $absolute = true) : string + { + if ($absolute == true) { + //TODO + throw new \Exception("Not Implemented yet..."); + //return str_replace('%BASE%', BASE, $this->db_data['filename_3d']); + } else { + return $this->filename_3d; + } + } + + /** + * Check if the filename of this footprint is valid (picture exists) + * + * This method is used to get all footprints with broken filename + * (Footprint::get_broken_filename_footprints()). + * + * @note An empty filename is a valid filename. + * + * @return boolean @li true if file exists or filename is empty + * @li false if there is no file with this filename + */ + public function isFilenameValid() : bool + { + if (empty($this->getFilename())) { + return true; + } + + return file_exists($this->getFilename()); + } + + /** + * Check if the filename of this 3d footprint is valid (model exists and have ) + * + * This method is used to get all footprints with broken 3d filename + * (Footprint::get_broken_3d_filename_footprints()). + * + * @note An empty filename is a valid filename. + * + * @return boolean @li true if file exists or filename is empty + * @li false if there is no file with this filename + */ + public function is3dFilenameValid() : bool + { + if (empty($this->get3dFilename())) { + return true; + } + + //Check if file is X3D-Model (these has .x3d extension) + if (strpos($this->get3dFilename(), '.x3d') == false) { + return false; + } + + return file_exists($this->get3dFilename()); + } + + /***************************************************************************** + * Setters + ****************************************************************************/ + + /******************************************************************************** + * + * Setters + * + *********************************************************************************/ + + /** + * Change the filename of this footprint + * + * @note The filename won't be checked if it is valid. + * It's not really a Problem if there is no such file... + * (For this purpose we have the method Footprint::get_broken_filename_footprints()) + * + * @param string $new_filename @li the new filename (absolute path from filesystem root, as a UNIX path [only slashes!] !! ) + * @li see also lib.functions.php::to_unix_path() + * + * @warning It's really important that you pass the whole (UNIX) path from filesystem root! + * If the file is located in the base directory of Part-DB, the base path + * will be automatically replaced with a placeholder before write it in the database. + * This way, the filenames are still correct if the installation directory + * of Part-DB is moved. + * + * @note The path-replacing will be done in Footprint::check_values_validity(), not here. + * + * @throws Exception if there was an error + */ + public function setFilename(string $new_filename) + { + $this->filename = $new_filename; + } + + /** + * Change the 3d model filename of this footprint + * @throws Exception if there was an error + */ + public function set3dFilename(string $new_filename) + { + $this->filename = $new_filename; + } +} \ No newline at end of file diff --git a/src/Entity/Manufacturer.php b/src/Entity/Manufacturer.php new file mode 100644 index 00000000..9867355a --- /dev/null +++ b/src/Entity/Manufacturer.php @@ -0,0 +1,47 @@ +getID()); + } +} \ No newline at end of file diff --git a/src/Entity/NamedDBElement.php b/src/Entity/NamedDBElement.php new file mode 100644 index 00000000..30aaca39 --- /dev/null +++ b/src/Entity/NamedDBElement.php @@ -0,0 +1,114 @@ +name); + } + + /** + * Returns the last time when the element was modified. + * @param $formatted bool When true, the date gets formatted with the locale and timezone settings. + * When false, the raw value from the DB is returned. + * @return string The time of the last edit. + */ + public function getLastModified(bool $formatted = true) : string + { + //TODO + return "TODO"; + } + + /** + * Returns the date/time when the element was created. + * @param $formatted bool When true, the date gets formatted with the locale and timezone settings. + * When false, the raw value from the DB is returned. + * @return string The creation time of the part. + */ + public function getDatetimeAdded(bool $formatted = true) : string + { + //TODO + return "TODO"; + } + + /******************************************************************************** + * + * Setters + * + *********************************************************************************/ + + /** + * Change the name of this element + * + * @note Spaces at the begin and at the end of the string will be removed + * automatically in NamedDBElement::check_values_validity(). + * So you don't have to do this yourself. + * + * @param string $new_name the new name + */ + public function setName(string $new_name) + { + $this->name = $new_name; + } + +} \ No newline at end of file diff --git a/src/Entity/Orderdetail.php b/src/Entity/Orderdetail.php new file mode 100644 index 00000000..a84f326f --- /dev/null +++ b/src/Entity/Orderdetail.php @@ -0,0 +1,266 @@ +getID()); + } + + /******************************************************************************** + * + * Getters + * + *********************************************************************************/ + + /** + * Get the part + * + * @return Part the part of this orderdetails + */ + public function getPart() : Part + { + return $this->part; + } + + /** + * Get the supplier + * + * @return Supplier the supplier of this orderdetails + * + * @throws DatabaseException if there was an error + */ + public function getSupplier() : Supplier + { + return $this->supplier; + } + + /** + * Get the supplier part-nr. + * + * @return string the part-nr. + */ + public function getSupplierPartNr() : string + { + return $this->supplierpartnr; + } + + /** + * Get if this orderdetails is obsolete + * + * "Orderdetails is obsolete" means that the part with that supplier-part-nr + * is no longer available from the supplier of that orderdetails. + * + * @return boolean @li true if this part is obsolete at that supplier + * @li false if this part isn't obsolete at that supplier + */ + public function getObsolete() : bool + { + return (bool) $this->obsolete; + } + + /** + * Get the link to the website of the article on the suppliers website. + * + * @param $no_automatic_url bool Set this to true, if you only want to get the local set product URL for this Orderdetail + * and not a automatic generated one, based from the Supplier + * + * @return string the link to the article + */ + public function getSupplierProductUrl(bool $no_automatic_url = false) : string + { + if ($no_automatic_url || $this->supplierpartnr != '') { + return $this->supplierpartnr; + } else { + return $this->getSupplier()->getAutoProductUrl($this->supplierpartnr); + } // maybe an automatic url is available... + } + + /** + * Get all pricedetails + * + * @return Pricedetails[] all pricedetails as a one-dimensional array of Pricedetails objects, + * sorted by minimum discount quantity + * + * @throws Exception if there was an error + */ + public function getPricedetails() : array + { + return $this->pricedetails; + } + + /** + * Get the price for a specific quantity + * + * @param boolean $as_money_string @li if true, this method returns a money string incl. currency + * @li if false, this method returns the price as float + * @param integer $quantity this is the quantity to choose the correct pricedetails + * @param integer|NULL $multiplier @li This is the multiplier which will be applied to every single price + * @li If you pass NULL, the number from $quantity will be used + * + * @return float|null|string float: the price as a float number (if "$as_money_string == false") + * * null: if there are no prices and "$as_money_string == false" + * * string: the price as a string incl. currency (if "$as_money_string == true") + * + * @throws Exception if there are no pricedetails for the choosed quantity + * (for example, there are only one pricedetails with the minimum discount quantity '10', + * but the choosed quantity is '5' --> the price for 5 parts is not defined!) + * @throws Exception if there was an error + * + * @see floatToMoneyString() + */ + public function getPrice(bool $as_money_string = false, int $quantity = 1, $multiplier = null) + { + /** + if (($quantity == 0) && ($multiplier === null)) { + if ($as_money_string) { + return floatToMoneyString(0); + } else { + return 0; + } + } + + $all_pricedetails = $this->getPricedetails(); + + if (count($all_pricedetails) == 0) { + if ($as_money_string) { + return floatToMoneyString(null); + } else { + return null; + } + } + + foreach ($all_pricedetails as $pricedetails) { + // choose the correct pricedetails for the choosed quantity ($quantity) + if ($quantity < $pricedetails->getMinDiscountQuantity()) { + break; + } + + $correct_pricedetails = $pricedetails; + } + + if (! isset($correct_pricedetails) || (! \is_object($correct_pricedetails))) { + throw new Exception(_('Es sind keine Preisinformationen für die angegebene Bestellmenge vorhanden!')); + } + + if ($multiplier === null) { + $multiplier = $quantity; + } + + return $correct_pricedetails->getPrice($as_money_string, $multiplier); + * */ + //TODO + throw new \Exception("Not implemented yet..."); + } + + /******************************************************************************** + * + * Setters + * + *********************************************************************************/ + + /** + * Set the supplier ID + * + * @param integer $new_supplier_id the ID of the new supplier + + */ + public function setSupplierId(int $new_supplier_id) + { + throw new \Exception("Not implemented yet!"); + //TODO; + } + + /** + * Set the supplier part-nr. + * + * @param string $new_supplierpartnr the new supplier-part-nr + * + */ + public function setSupplierpartnr(string $new_supplierpartnr) + { + $this->supplierpartnr = $new_supplierpartnr; + } + + /** + * Set if the part is obsolete at the supplier of that orderdetails + * + * @param boolean $new_obsolete true means that this part is obsolete + */ + public function setObsolete(bool $new_obsolete) + { + $this->obsolete = $new_obsolete; + } + + /** + * Sets the custom product supplier URL for this order detail. + * Set this to "", if the function getSupplierProductURL should return the automatic generated URL. + * @param $new_url string The new URL for the supplier URL. + * @throws Exception if there was an error + */ + public function setSupplierProductUrl(string $new_url) + { + $this->setAttributes(array('supplier_product_url' => $new_url)); + } +} \ No newline at end of file diff --git a/src/Entity/Part.php b/src/Entity/Part.php new file mode 100644 index 00000000..809b008c --- /dev/null +++ b/src/Entity/Part.php @@ -0,0 +1,1050 @@ +getID()); + } + + + /********************************************************************************* + * Getters + ********************************************************************************/ + + /** + * Get the description + * + * @param boolean|int $bbcode_parse_level Should BBCode converted to HTML, before returning + * @param int $short_output If this is bigger than 0, than the description will be shortened to this length. + * @return string the description + */ + public function getDescription($bbcode_parse_level = 0/*BBCodeParsingLevel::PARSE*/, int $short_output = 0) : string + { + $val = htmlspecialchars($this->description); + + if ($short_output > 0 && \strlen($val) > $short_output) { + $val = substr($val, 0, $short_output); + $val .= '...'; + $val = '' . $val . ''; + } + + //TODO + /** + if ($bbcode_parse_level === BBCodeParsingLevel::PARSE) { + $bbcode = new BBCodeParser(); + $val = $bbcode->only('bold', 'italic', 'underline', 'linethrough')->parse($val); + } elseif ($bbcode_parse_level === BBCodeParsingLevel::STRIP) { + $bbcode = new BBCodeParser(); + $val = $bbcode->stripBBCodeTags($val); + }*/ + + return $val; + } + + /** + * Get the count of parts which are in stock + * @param $with_unknown bool Set this, to true, if the unknown state should be returned as string. Otherwise -2 is returned. + * + * @return integer|string count of parts which are in stock, "Unknown" if $with_unknown is set and instock is unknown. + */ + public function getInstock(bool $with_unknown = false) : int + { + + if ($with_unknown && $this->isInstockUnknown()) { + return _('[Unbekannt]'); + } + + return $this->instock; + } + + /** + * Check if the value of the Instock is unknown. + * @return bool True, if the value of the instock is unknown. + */ + public function isInstockUnknown() : bool + { + return $this->instock <= static::INSTOCK_UNKNOWN; + }/** @noinspection ReturnTypeCanBeDeclaredInspection */ + + /** + * Get the count of parts which must be in stock at least + * + * @return integer count of parts which must be in stock at least + */ + public function getMinInstock() : int + { + return $this->mininstock; + } + + /** + * Get the comment + * + * @param boolean|int $bbcode_parsing_level Should BBCode converted to HTML, before returning + * @return string the comment + */ + public function getComment($bbcode_parsing_level = BBCodeParsingLevel::PARSE) : string + { + + $val = htmlspecialchars($this->comment); + + //TODO + /*if ($bbcode_parsing_level === BBCodeParsingLevel::PARSE) { + $bbcode = new BBCodeParser(); + $bbcode->setParser('brLinebreak', "/\[br\]/s", '
', ''); + $bbcode->setParser('namedlink', '/\[url\=(.*?)\](.*?)\[\/url\]/s', '$2', '$2'); + $bbcode->setParser('link', '/\[url\](.*?)\[\/url\]/s', '$1', '$1'); + $val = $bbcode->parse($val); + } elseif ($bbcode_parsing_level === BBCodeParsingLevel::STRIP) { + $bbcode = new BBCodeParser(); + $val = str_replace("\n", ' ', $val); + $val = $bbcode->stripBBCodeTags($val); + }*/ + + return $val; + } + + /** + * Get if this part is obsolete + * + * @note A Part is marked as "obsolete" if all their orderdetails are marked as "obsolete". + * If a part has no orderdetails, the part isn't marked as obsolete. + * + * @return boolean @li true if this part is obsolete + * @li false if this part isn't obsolete + * @throws Exception + */ + public function getObsolete() : bool + { + $all_orderdetails = $this->getOrderdetails(); + + if (count($all_orderdetails) == 0) { + return false; + } + + foreach ($all_orderdetails as $orderdetails) { + if (! $orderdetails->getObsolete()) { + return false; + } + } + + return true; + } + + /** + * Get if this part is visible + * + * @return boolean @li true if this part is visible + * @li false if this part isn't visible + */ + public function getVisible() : bool + { + return (bool) $this->visible; + } + + /** + * Get if this part is a favorite. + * + * @return bool * true if this part is a favorite + * * false if this part is not a favorite. + */ + public function getFavorite() : bool + { + return (bool) $this->favorite; + } + + /** + * Get the selected order orderdetails of this part + * + * @return Orderdetail the selected order orderdetails + * @return NULL if there is no order supplier selected + * @throws Exception + */ + public function getOrderOrderdetails() : ?Orderdetail + { + //TODO + /* + if ($this->order_orderdetails->getObsolete()) { + $this->setOrderOrderdetailsID(null); + $this->order_orderdetails = null; + }*/ + + return $this->order_orderdetail; + } + + /** + * Get the order quantity of this part + * + * @return integer the order quantity + */ + public function getOrderQuantity() : int + { + return (int) $this->order_quantity; + } + + /** + * Get the minimum quantity which should be ordered + * + * @param boolean $with_devices @li if true, all parts from devices which are marked as "to order" will be included in the calculation + * @li if false, only max(mininstock - instock, 0) will be returned + * + * @return integer the minimum order quantity + * @throws Exception + */ + public function getMinOrderQuantity(bool $with_devices = true) : int + { + //TODO + throw new \Exception("Not implemented yet..."); + + /** + if ($with_devices) { + $count_must_order = 0; // for devices with "order_only_missing_parts == false" + $count_should_order = 0; // for devices with "order_only_missing_parts == true" + $deviceparts = DevicePart::getOrderDeviceParts($this->database, $this->current_user, $this->log, $this->getID()); + foreach ($deviceparts as $devicepart) { + /** @var $devicepart DevicePart */ + /** @var $device Device */ /** + $device = $devicepart->getDevice(); + if ($device->getOrderOnlyMissingParts()) { + $count_should_order += $device->getOrderQuantity() * $devicepart->getMountQuantity(); + } else { + $count_must_order += $device->getOrderQuantity() * $devicepart->getMountQuantity(); + } + } + + return $count_must_order + max(0, $this->getMinInstock() - $this->getInstock() + $count_should_order); + } else { + return max(0, $this->getMinInstock() - $this->getInstock()); + } **/ + } + + /** + * Get the "manual_order" attribute + * + * @return boolean the "manual_order" attribute + */ + public function getManualOrder() : bool + { + return (bool) $this->manual_order; + } + + /** + * Check if the part is automatically marked for Ordering, because the instock value is smaller than the min instock value. + * @return bool True, if the part should be ordered. + */ + public function getAutoOrder() : bool + { + //Parts with negative instock never gets ordered. + if ($this->getInstock() < 0) { + return false; + } + + return $this->getInstock() < $this->getMinInstock(); + } + + /** + * Get the link to the website of the article on the manufacturers website + * + * @param + * + * @return string the link to the article + * @throws Exception + */ + public function getManufacturerProductUrl(bool $no_auto_url = false) : string + { + if ($no_auto_url || $this->manufacturer_product_url != '') { + return $this->manufacturer_product_url; + } elseif (\is_object($this->getManufacturer())) { + return $this->getManufacturer()->getAutoProductUrl($this->name); + } else { + return ''; + } // no url is available + } + + /** + * Get the category of this part + * + * There is always a category, for each part! + * + * @return Category the category of this part + */ + public function getCategory() : Category + { + return $this->category; + } + + /** + * Get the footprint of this part (if there is one) + * + * @return Footprint the footprint of this part (if there is one) + * @return NULL if this part has no footprint + */ + public function getFootprint() : ?Footprint + { + return $this->footprint; + } + + /** + * Get the storelocation of this part (if there is one) + * + * @return Storelocation the storelocation of this part (if there is one) + * @return NULL if this part has no storelocation + */ + public function getStorelocation() : ?Storelocation + { + return $this->storelocation; + } + + /** + * Get the manufacturer of this part (if there is one) + * + * @return Manufacturer the manufacturer of this part (if there is one) + * @return NULL if this part has no manufacturer + */ + public function getManufacturer() : ?Manufacturer + { + return $this->manufacturer; + } + + /** + * Get the master picture "Attachement"-object of this part (if there is one) + * + * @return Attachment the master picture Attachement of this part (if there is one) + * @return NULL if this part has no master picture + */ + public function getMasterPictureAttachement() : ?Attachment + { + return $this->master_picture_attachement; + } + + /** + * Get all orderdetails of this part + * + * @param boolean $hide_obsolete If true, obsolete orderdetails will NOT be returned + * + * @return Orderdetails[] @li all orderdetails as a one-dimensional array of Orderdetails objects + * (empty array if there are no ones) + * @li the array is sorted by the suppliers names / minimum order quantity + * + * @throws Exception if there was an error + */ + public function getOrderdetails(bool $hide_obsolete = false) + { + if ($hide_obsolete) { + $orderdetails = $this->orderdetails; + foreach ($orderdetails as $key => $details) { + if ($details->getObsolete()) { + unset($orderdetails[$key]); + } + } + return $orderdetails; + } else { + return $this->orderdetails; + } + } + + /** + * Get all devices which uses this part + * + * @return Device[] @li all devices which uses this part as a one-dimensional array of Device objects + * (empty array if there are no ones) + * @li the array is sorted by the devices names + * + * @throws Exception if there was an error + */ + public function getDevices() : array + { + return $this->devices; + } + + /** + * Get all suppliers of this part + * + * This method simply gets the suppliers of the orderdetails and prepare them.\n + * You can get the suppliers as an array or as a string with individual delimeter. + * + * @param boolean $object_array @li if true, this method returns an array of Supplier objects + * @li if false, this method returns an array of strings + * @param string|NULL $delimeter @li if this is a string and "$object_array == false", + * this method returns a string with all + * supplier names, delimeted by "$delimeter" + * @param boolean $full_paths @li if true and "$object_array = false", the returned + * suppliernames are full paths (path + name) + * @li if true and "$object_array = false", the returned + * suppliernames are only the names (without path) + * @param boolean $hide_obsolete If true, suppliers from obsolete orderdetails will NOT be returned + * + * @return array all suppliers as a one-dimensional array of Supplier objects + * (if "$object_array == true") + * @return array all supplier-names as a one-dimensional array of strings + * ("if $object_array == false" and "$delimeter == NULL") + * @return string a sting of all supplier names, delimeted by $delimeter + * ("if $object_array == false" and $delimeter is a string) + * + * @throws Exception if there was an error + */ + public function getSuppliers(bool $object_array = true, $delimeter = null, bool $full_paths = false, bool $hide_obsolete = false) + { + $suppliers = array(); + $orderdetails = $this->getOrderdetails($hide_obsolete); + + foreach ($orderdetails as $details) { + $suppliers[] = $details->getSupplier(); + } + + if ($object_array) { + return $suppliers; + } else { + $supplier_names = array(); + foreach ($suppliers as $supplier) { + /** @var Supplier $supplier */ + if ($full_paths) { + $supplier_names[] = $supplier->getFullPath(); + } else { + $supplier_names[] = $supplier->getName(); + } + } + + if (\is_string($delimeter)) { + return implode($delimeter, $supplier_names); + } else { + return $supplier_names; + } + } + } + + /** + * Get all supplier-part-Nrs + * + * This method simply gets the suppliers-part-Nrs of the orderdetails and prepare them.\n + * You can get the numbers as an array or as a string with individual delimeter. + * + * @param string|NULL $delimeter @li if this is a string, this method returns a delimeted string + * @li otherwise, this method returns an array of strings + * @param boolean $hide_obsolete If true, supplierpartnrs from obsolete orderdetails will NOT be returned + * + * @return array all supplierpartnrs as an array of strings (if "$delimeter == NULL") + * @return string all supplierpartnrs as a string, delimeted ba $delimeter (if $delimeter is a string) + * + * @throws Exception if there was an error + */ + public function getSupplierPartNrs($delimeter = null, bool $hide_obsolete = false) + { + $supplierpartnrs = array(); + + foreach ($this->getOrderdetails($hide_obsolete) as $details) { + $supplierpartnrs[] = $details->getSupplierPartNr(); + } + + if (\is_string($delimeter)) { + return implode($delimeter, $supplierpartnrs); + } else { + return $supplierpartnrs; + } + } + + /** + * Get all prices of this part + * + * This method simply gets the prices of the orderdetails and prepare them.\n + * In the returned array/string there is a price for every supplier. + * + * @param boolean $float_array @li if true, the returned array is an array of floats + * @li if false, the returned array is an array of strings + * @param string|NULL $delimeter if this is a string, this method returns a delimeted string + * instead of an array. + * @param integer $quantity this is the quantity to choose the correct priceinformation + * @param integer|NULL $multiplier @li This is the multiplier which will be applied to every single price + * @li If you pass NULL, the number from $quantity will be used + * @param boolean $hide_obsolete If true, prices from obsolete orderdetails will NOT be returned + * + * @return array all prices as an array of floats (if "$delimeter == NULL" & "$float_array == true") + * @return array all prices as an array of strings (if "$delimeter == NULL" & "$float_array == false") + * @return string all prices as a string, delimeted by $delimeter (if $delimeter is a string) + * + * @warning If there are orderdetails without prices, for these orderdetails there + * will be a "NULL" in the returned float array (or a "-" in the string array)!! + * (This is needed for the HTML output, if there are all orderdetails and prices listed.) + * + * @throws Exception if there was an error + */ + public function getPrices(bool $float_array = false, $delimeter = null, int $quantity = 1, $multiplier = null, bool $hide_obsolete = false) + { + $prices = array(); + + foreach ($this->getOrderdetails($hide_obsolete) as $details) { + $prices[] = $details->getPrice(! $float_array, $quantity, $multiplier); + } + + if (\is_string($delimeter)) { + return implode($delimeter, $prices); + } else { + return $prices; + } + } + + /** + * Get the average price of all orderdetails + * + * With the $multiplier you're able to multiply the price before it will be returned. + * This is useful if you want to have the price as a string with currency, but multiplied with a factor. + * + * @param boolean $as_money_string @li if true, the retruned value will be a string incl. currency, + * ready to print it out. See float_to_money_string(). + * @li if false, the returned value is a float + * @param integer $quantity this is the quantity to choose the correct priceinformations + * @param integer|NULL $multiplier @li This is the multiplier which will be applied to every single price + * @li If you pass NULL, the number from $quantity will be used + * + * @return float price (if "$as_money_string == false") + * @return NULL if there are no prices for this part and "$as_money_string == false" + * @return string price with currency (if "$as_money_string == true") + * + * @throws Exception if there was an error + */ + public function getAveragePrice(bool $as_money_string = false, int $quantity = 1, $multiplier = null) + { + $prices = $this->getPrices(true, null, $quantity, $multiplier, true); + $average_price = null; + + $count = 0; + foreach ($prices as $price) { + if ($price !== null) { + $average_price += $price; + $count++; + } + } + + if ($count > 0) { + $average_price /= $count; + } + + if ($as_money_string) { + return floatToMoneyString($average_price); + } else { + return $average_price; + } + } + + /** + * Get the filename of the master picture (absolute path from filesystem root) + * + * @param boolean $use_footprint_filename @li if true, and this part has no picture, this method + * will return the filename of its footprint (if available) + * @li if false, and this part has no picture, + * this method will return NULL + * + * @return string the whole path + filename from filesystem root as a UNIX path (with slashes) + * @return NULL if there is no picture + * + * @throws Exception if there was an error + */ + public function getMasterPictureFilename(bool $use_footprint_filename = false) + { + $master_picture = $this->getMasterPictureAttachement(); // returns an Attachement-object + + if (\is_object($master_picture)) { + return $master_picture->getFilename(); + } + + if ($use_footprint_filename) { + $footprint = $this->getFootprint(); + if (\is_object($footprint)) { + return $footprint->getFilename(); + } + } + + return null; + } + + /** + * Parses the selected fields and extract Properties of the part. + * @param bool $use_description Use the description field for parsing + * @param bool $use_comment Use the comment field for parsing + * @param bool $use_name Use the name field for parsing + * @param bool $force_output Properties are parsed even if properties are disabled. + * @return array A array of PartProperty objects. + * @return array If Properties are disabled or nothing was detected, then an empty array is returned. + * @throws Exception + */ + public function getProperties(bool $use_description = true, bool $use_comment = true, bool $use_name = true, bool $force_output = false) : array + { + //TODO + throw new \Exception("Not implemented yet!"); + /* + global $config; + + if ($config['properties']['active'] || $force_output) { + if ($this->getCategory()->getDisableProperties(true)) { + return array(); + } + + $name = array(); + $desc = array(); + $comm = array(); + + if ($use_name === true) { + $name = $this->getCategory()->getPartnameRegexObj()->getProperties($this->getName()); + } + if ($use_description === true) { + $desc = PartProperty::parseDescription($this->getDescription()); + } + if ($use_comment === true) { + $comm = PartProperty::parseDescription($this->getComment(false)); + } + + return array_merge($name, $desc, $comm); + } else { + return array(); + }*/ + } + + /** + * Returns a loop (array) of the array representations of the properties of this part. + * @param bool $use_description Use the description field for parsing + * @param bool $use_comment Use the comment field for parsing + * @return array A array of arrays with the name and value of the properties. + */ + public function getPropertiesLoop(bool $use_description = true, bool $use_comment = true, bool $use_name = true) : array + { + //TODO + throw new \Exception("Not implemented yet!"); + $arr = array(); + foreach ($this->getProperties($use_description, $use_comment, $use_name) as $property) { + /* @var PartProperty $property */ + $arr[] = $property->getArray(true); + } + return $arr; + } + + /* + public function hasValidName() : bool + { + return self::isValidName($this->getName(), $this->getCategory()); + } */ + + + public function getAttachmentTypes() : array + { + return parent::getAttachmentTypes(); + } + + public function getAttachments($type_id = null, bool $only_table_attachements = false) : array + { + return parent::getAttachments($type_id, $only_table_attachements); + } + + /******************************************************************************** + * + * Setters + * + *********************************************************************************/ + + /** + * Set the description + * + * @param string $new_description the new description + * + * @throws Exception if there was an error + */ + public function setDescription(string $new_description) + { + $this->description = $new_description; + } + + /** + * Set the count of parts which are in stock + * + * @param integer $new_instock the new count of parts which are in stock + * + * @throws Exception if the new instock is not valid + * @throws Exception if there was an error + */ + public function setInstock(int $new_instock, $comment = null) + { + $old_instock = (int) $this->getInstock(); + $this->instock = $new_instock; + //TODO + /* + InstockChangedEntry::add( + $this->database, + $this->current_user, + $this->log, + $this, + $old_instock, + $new_instock, + $comment + );*/ + } + + /** + * Withdrawal the given number of parts. + * @param $count int The number of parts which should be withdrawan. + * @param $comment string A comment that should be associated with the withdrawal. + * @throws Exception if there was an error + */ + public function withdrawalParts(int $count, $comment = null) + { + if ($count <= 0) { + throw new \Exception('Zahl der entnommenen Bauteile muss größer 0 sein!'); + } + if ($count > $this->getInstock()) { + throw new Exception('Es können nicht mehr Bauteile entnommen werden, als vorhanden sind!'); + } + + $old_instock = (int) $this->getInstock(); + $new_instock = $old_instock - $count; + + //TODO + /* + InstockChangedEntry::add( + $this->database, + $this->current_user, + $this->log, + $this, + $old_instock, + $new_instock, + $comment + );*/ + + $this->instock = $new_instock; + } + + /** + * Add the given number of parts. + * @param $count int The number of parts which should be withdrawan. + * @param $comment string A comment that should be associated with the withdrawal. + * @throws Exception if there was an error + */ + public function addParts(int $count, string $comment = null) + { + + //TODO + if ($count <= 0) { + throw new Exception('Zahl der entnommenen Bauteile muss größer 0 sein!'); + } + + $old_instock = (int) $this->getInstock(); + $new_instock = $old_instock + $count; + + //TODO + /* + InstockChangedEntry::add( + $this->database, + $this->current_user, + $this->log, + $this, + $old_instock, + $new_instock, + $comment + );*/ + + $this->instock = $new_instock; + } + + /** + * Set the count of parts which should be in stock at least + * + * @param integer $new_mininstock the new count of parts which should be in stock at least + * + * @throws Exception if the new mininstock is not valid + * @throws Exception if there was an error + */ + public function setMinInstock(int $new_mininstock) + { + $this->mininstock = $new_mininstock; + } + + /** + * Set the comment + * + * @param string $new_comment the new comment + * + * @throws Exception if there was an error + */ + public function setComment(string $new_comment) + { + $this->comment = $new_comment; + } + + /** + * Set the "manual_order" attribute + * + * @param boolean $new_manual_order the new "manual_order" attribute + * @param integer $new_order_quantity the new order quantity + * @param integer|NULL $new_order_orderdetails_id @li the ID of the new order orderdetails + * @li or Zero for "no order orderdetails" + * @li or NULL for automatic order orderdetails + * (if the part has exactly one orderdetails, + * set this orderdetails as order orderdetails. + * Otherwise, set "no order orderdetails") + * + * @throws Exception if there was an error + */ + public function setManualOrder(bool $new_manual_order, int $new_order_quantity = 1, $new_order_orderdetails_id = null) + { + $this->manual_order = $new_manual_order; + //TODO; + /* $this->order_orderdetail = $new_order_orderdetails_id; */ + $this->order_quantity = $new_order_quantity; + } + + /** + * Set the ID of the order orderdetails + * + * @param integer|NULL $new_order_orderdetails_id @li the new order orderdetails ID + * @li Or, to remove the orderdetails, pass a NULL + * + * @throws Exception if there was an error + */ + public function setOrderOrderdetailsID($new_order_orderdetails_id) : void + { + //TODO + throw new \Exception("Not implemented yet..."); + } + + /** + * Set the order quantity + * + * @param integer $new_order_quantity the new order quantity + */ + public function setOrderQuantity(int $new_order_quantity) : void + { + $this->order_quantity = $new_order_quantity; + } + + /** + * Set the ID of the category + * + * @note Every part must have a valid category (in contrast to the + * attributes "footprint", "storelocation", ...)! + * + * @param integer $new_category_id the ID of the category + * + * @throws Exception if the new category ID is not valid + * @throws Exception if there was an error + */ + public function setCategoryID(int $new_category_id) + { + //TODO + throw new \Exception("Not implemented yet!"); + } + + /** + * Set the footprint ID + * + * @param integer|NULL $new_footprint_id @li the ID of the footprint + * @li NULL means "no footprint" + * + * @throws Exception if the new footprint ID is not valid + * @throws Exception if there was an error + */ + public function setFootprintID($new_footprint_id) + { + //TODO + throw new \Exception("Not implemented yet!"); + } + + /** + * Set the storelocation ID + * + * @param integer|NULL $new_storelocation_id @li the ID of the storelocation + * @li NULL means "no storelocation" + * + * @throws Exception if the new storelocation ID is not valid + * @throws Exception if there was an error + */ + public function setStorelocationID($new_storelocation_id) + { + //TODO + throw new \Exception("Not implemented yet!"); + } + + /** + * Set the manufacturer ID + * + * @param integer|NULL $new_manufacturer_id @li the ID of the manufacturer + * @li NULL means "no manufacturer" + * + * @throws Exception if the new manufacturer ID is not valid + * @throws Exception if there was an error + */ + public function setManufacturerID($new_manufacturer_id) + { + //TODO + throw new \Exception("Not implemented yet!"); + } + + /** + * Set the favorite status for this part. + * @param $new_favorite_status bool The new favorite status, that should be applied on this part. + * Set this to true, when the part should be a favorite. + */ + public function setFavorite(bool $new_favorite_status) + { + $this->favorite = $new_favorite_status; + } + + /** + * Sets the URL to the manufacturer site about this Part. Set to "" if this part should use the automatically URL based on its manufacturer. + * @param string $new_url The new url + * @throws Exception when an error happens. + */ + public function setManufacturerProductURL(string $new_url) + { + $this->manufacturer_product_url = $new_url; + } + + /** + * Set the ID of the master picture Attachement + * + * @param integer|NULL $new_master_picture_attachement_id @li the ID of the Attachement object of the master picture + * @li NULL means "no master picture" + * + * @throws Exception if the new ID is not valid + * @throws Exception if there was an error + */ + public function setMasterPictureAttachementID($new_master_picture_attachement_id) + { + //TODO + throw new \Exception("Not implemented yet!"); + } + + +} \ No newline at end of file diff --git a/src/Entity/PartsContainingDBElement.php b/src/Entity/PartsContainingDBElement.php new file mode 100644 index 00000000..14f74845 --- /dev/null +++ b/src/Entity/PartsContainingDBElement.php @@ -0,0 +1,39 @@ +orderdetail; + } + + /** + * Get the price + * + * @param boolean $as_money_string @li if true, this method returns a money string incl. currency + * @li if false, this method returns the price as float + * @param integer $multiplier The returned price (float or string) will be multiplied + * with this multiplier. + * + * @note You will get the price for $multiplier parts. If you want the price which is stored + * in the database, you have to pass the "price_related_quantity" count as $multiplier. + * + * @return float the price as a float number (if "$as_money_string == false") + * @return string the price as a string incl. currency (if "$as_money_string == true") + * + * @see floatToMoneyString() + */ + public function getPrice(bool $as_money_string = false, int $multiplier = 1) + { + $price = ($this->price * $multiplier) / $this->price_related_quantity; + + if ($as_money_string) { + throw new \Exception("Not implemented yet..."); + //return floatToMoneyString($price); + } else { + return $price; + } + } + + /** + * Get the price related quantity + * + * This is the quantity, for which the price is valid. + * + * @return integer the price related quantity + * + * @see Pricedetails::setPriceRelatedQuantity() + */ + public function getPriceRelatedQuantity() : int + { + return (int) $this->price_related_quantity; + } + + /** + * Get the minimum discount quantity + * + * "Minimum discount quantity" means the minimum order quantity for which the price + * of this orderdetails is valid. + * + * @return integer the minimum discount quantity + * + * @see Pricedetails::setMinDiscountQuantity() + */ + public function getMinDiscountQuantity() : int + { + return (int) $this->min_discount_quantity; + } + + /******************************************************************************** + * + * Setters + * + *********************************************************************************/ + + /** + * Set the price + * + * @param float $new_price the new price as a float number + * + * @warning @li This is the price for "price_related_quantity" parts!! + * @li Example: if "price_related_quantity" is '10', + * you have to set here the price for 10 parts! + */ + public function setPrice(float $new_price) : void + { + if($new_price < 0) + { + throw new \InvalidArgumentException('$new_price must be positive!'); + } + $this->price = $new_price; + } + + /** + * Set the price related quantity + * + * This is the quantity, for which the price is valid. + * + * @par Example: + * If 100pcs costs 20$, you have to set the price to 20$ and the price related + * quantity to 100. The single price (20$/100 = 0.2$) will be calculated automatically. + * + * @param integer $new_price_related_quantity the price related quantity + */ + public function setPriceRelatedQuantity(int $new_price_related_quantity) : void + { + if($new_price_related_quantity <= 0) { + throw new \InvalidArgumentException('$new_price_related_quantity must be greater 0!'); + } + $this->price_related_quantity = $new_price_related_quantity; + } + + /** + * Set the minimum discount quantity + * + * "Minimum discount quantity" means the minimum order quantity for which the price + * of this orderdetails is valid. This way, you're able to use different prices + * for different order quantities (quantity discount!). + * + * @par Example: + * - 1-9pcs costs 10$: set price to 10$/pcs and minimum discount quantity to 1 + * - 10-99pcs costs 9$: set price to 9$/pcs and minimum discount quantity to 10 + * - 100pcs or more costs 8$: set price/pcs to 8$ and minimum discount quantity to 100 + * + * (Each of this examples would be an own Pricedetails-object. + * So the orderdetails would have three Pricedetails for one supplier.) + * + * @param integer $new_min_discount_quantity the minimum discount quantity + */ + public function setMinDiscountQuantity(int $new_min_discount_quantity) + { + if($new_min_discount_quantity <= 0 ){ + throw new \InvalidArgumentException('$new_min_discount_quantity must be positive!'); + } + $this->min_discount_quantity = $new_min_discount_quantity; + } + + + + + /** + * Returns the ID as an string, defined by the element class. + * This should have a form like P000014, for a part with ID 14. + * @return string The ID as a string; + */ + public function getIDString(): string + { + return 'PD' . sprintf('%06d', $this->getID()); + } +} \ No newline at end of file diff --git a/src/Entity/Storelocation.php b/src/Entity/Storelocation.php new file mode 100644 index 00000000..1ea67a54 --- /dev/null +++ b/src/Entity/Storelocation.php @@ -0,0 +1,89 @@ +is_full; + } + + /******************************************************************************** + * + * Setters + * + *********************************************************************************/ + + /** + * Change the "is full" attribute of this storelocation + * + * @note "is_full" = true means that there is no more space in this storelocation. + * @note This attribute is only for information, it has no effect. + * + * @param boolean $new_is_full @li true means that the storelocation is full + * @li false means that the storelocation isn't full + * + * @throws Exception if there was an error + */ + public function setIsFull(bool $new_is_full) : void + { + $this->is_full = $new_is_full; + } + + /** + * Returns the ID as an string, defined by the element class. + * This should have a form like P000014, for a part with ID 14. + * @return string The ID as a string; + */ + public function getIDString(): string + { + return 'L' . sprintf('%06d', $this->getID()); + } +} \ No newline at end of file diff --git a/src/Entity/StructuralDBElement.php b/src/Entity/StructuralDBElement.php new file mode 100644 index 00000000..25c507a1 --- /dev/null +++ b/src/Entity/StructuralDBElement.php @@ -0,0 +1,416 @@ +getID() == null) { // this is the root node + return false; + } else { + //If this' parents element, is $another_element, then we are finished + return (($this->parent->getID() == $another_element->getID()) + || $this->parent->isChildOf($another_element)); //Otherwise, check recursivley + } + } + + + /****************************************************************************** + * + * Getters + * + ******************************************************************************/ + + /** + * @brief Get the parent-ID + * + * @retval integer @li the ID of the parent element + * @li NULL means, the parent is the root node + * @li the parent ID of the root node is -1 + */ + public function getParentID() : int + { + return $this->parent_id ?? self::ID_ROOT_ELEMENT; //Null means root element + } + + /** + * Get the comment of the element. + * + * @param boolean $parse_bbcode Should BBCode converted to HTML, before returning + * @return string the comment + */ + public function getComment(bool $parse_bbcode = true) : string + { + $val = htmlspecialchars($this->comment ?? ''); + if ($parse_bbcode) { + //$bbcode = new BBCodeParser(); + //$val = $bbcode->parse($val); + } + + return $val; + } + + /** + * Get the level + * + * @note The level of the root node is -1. + * + * @return integer the level of this element (zero means a most top element + * [a subelement of the root node]) + * + */ + public function getLevel() : int + { + if ($this->level === 0) { + $element = $this->parent; + $parent_id = $element->getParentID(); + while ($parent_id > 0) { + /** @var StructuralDBElement $element */ + $element = $element->parent; + $parent_id = $element->getParentID(); + $this->level++; + } + } + + return $this->level; + } + + /** + * Get the full path + * + * @param string $delimeter the delimeter of the returned string + * + * @return string the full path (incl. the name of this element), delimeted by $delimeter + * + * @throws Exception if there was an error + */ + public function getFullPath(string $delimeter = self::PATH_DELIMITER_ARROW) : string + { + if (! \is_array($this->full_path_strings)) { + $this->full_path_strings = array(); + $this->full_path_strings[] = $this->getName(); + $parent_id = $this->getParentID(); + while ($parent_id > 0) { + /** @var StructuralDBElement $element */ + $element = static::getInstance($this->database, $this->current_user, $this->log, $parent_id); + $parent_id = $element->getParentID(); + $this->full_path_strings[] = $element->getName(); + } + $this->full_path_strings = array_reverse($this->full_path_strings); + } + + return implode($delimeter, $this->full_path_strings); + } + + /** + * Get all subelements of this element + * + * @param boolean $recursive if true, the search is recursive + * + * @return static[] all subelements as an array of objects (sorted by their full path) + */ + public function getSubelements(bool $recursive) : PersistentCollection + { + if ($this->children == null) { + $this->children = new \Doctrine\Common\Collections\ArrayCollection(); + } + + if (! $recursive) { + return $this->children; + } else { + $all_elements = array(); + foreach ($this->children as $subelement) { + $all_elements[] = $subelement; + $all_elements = array_merge($all_elements, $subelement->getSubelements(true)); + } + + return $all_elements; + } + } + + /****************************************************************************** + * + * Setters + * + ******************************************************************************/ + + /** + * Change the parent ID of this element + * + * @param integer|null $new_parent_id @li the ID of the new parent element + * @li NULL if the parent should be the root node + */ + public function setParentID($new_parent_id) + { + $this->parent_id = $new_parent_id; + } + + /** + * Set the comment + * + * @param string $new_comment the new comment + * @throws Exception if there was an error + */ + public function setComment(string $new_comment) + { + $this->comment = $new_comment; + } + + /******************************************************************************** + * + * Tree / Table Builders + * + *********************************************************************************/ + + /** + * Build a HTML tree with all subcategories of this element + * + * This method prints a