 sunnysideup    /
                    silverstripe-ecommerce_combo_product
                      sunnysideup    /
                    silverstripe-ecommerce_combo_product
                
                            This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
                                via PHP's auto-loading mechanism.
                                                    These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * A combination product combines several products into one new product. | ||
| 4 | * | ||
| 5 | * The way this works is that the combo product links to zero or more products (many many relationship) | ||
| 6 | * | ||
| 7 | * When you add the combo product, the individual products are added. A modifier adds the "discount" for the combo. | ||
| 8 | * It also deletes all the products as soon as you delete one. | ||
| 9 | * | ||
| 10 | * There are two restrictions to keep in mind: | ||
| 11 | * (a) it only applies to products and not all buyables. | ||
| 12 | * (b) all products need to return true for canPurchase. This means that if the product is available as part of a combo, it should also be available by itself. | ||
| 13 | * | ||
| 14 | * We use the sort order for the order attribute to group it... | ||
| 15 | * | ||
| 16 | * @package: ecommerce | ||
| 17 | * @sub-package: products | ||
| 18 | * | ||
| 19 | **/ | ||
| 20 | |||
| 21 | |||
| 22 | class CombinationProduct extends Product | ||
| 0 ignored issues–
                            show | |||
| 23 | { | ||
| 24 | private static $db = array( | ||
| 0 ignored issues–
                            show | |||
| 25 | 'NewPrice' => 'Currency' | ||
| 26 | ); | ||
| 27 | |||
| 28 | private static $many_many = array( | ||
| 0 ignored issues–
                            show | |||
| 29 | 'IncludedProducts' => 'Product' | ||
| 30 | ); | ||
| 31 | |||
| 32 | private static $searchable_fields = array( | ||
| 0 ignored issues–
                            show | |||
| 33 | 'ID', | ||
| 34 | 'Title', | ||
| 35 | 'InternalItemID', | ||
| 36 | 'Price', | ||
| 37 | 'ListOfProducts' | ||
| 38 | ); | ||
| 39 | |||
| 40 | private static $casting = array( | ||
| 0 ignored issues–
                            show | |||
| 41 | "OriginalPrice" => "Currency" | ||
| 42 | ); | ||
| 43 | |||
| 44 | private static $singular_name = "Combination Product"; | ||
| 0 ignored issues–
                            show | |||
| 45 | public function i18n_singular_name() | ||
| 46 |     { | ||
| 47 |         return _t("CombinationProduct.COMBINATIONPRODUCT", "Combination Product"); | ||
| 48 | } | ||
| 49 | |||
| 50 | private static $plural_name = "Combination Products"; | ||
| 0 ignored issues–
                            show | |||
| 51 | public function i18n_plural_name() | ||
| 52 |     { | ||
| 53 |         return _t("CombinationProduct.COMBINATIONPRODUCTS", "Combination Products"); | ||
| 54 | } | ||
| 55 | |||
| 56 | private static $default_parent = 'ProductGroup'; | ||
| 0 ignored issues–
                            show | |||
| 57 | |||
| 58 | private static $default_sort = '"Title" ASC'; | ||
| 0 ignored issues–
                            show | |||
| 59 | |||
| 60 | private static $icon = 'ecommerce_combo_product/images/icons/CombinationProduct'; | ||
| 0 ignored issues–
                            show | |||
| 61 | |||
| 62 | public function getCMSFields() | ||
| 0 ignored issues–
                            show The return type could not be reliably inferred; please add a  @returnannotation.Our type inference engine in quite powerful, but sometimes the code does not
provide enough clues to go by. In these cases we request you to add a   Loading history... | |||
| 63 |     { | ||
| 64 | $fields = parent::getCMSFields(); | ||
| 65 |         $fields->addFieldToTab("Root.Components", $this->getIncludedProductsFormField()); | ||
| 66 |         $fields->replaceField("Price", new ReadOnlyField("Price", "Full Price")); | ||
| 67 |         $fields->addFieldToTab("Root.Details", new NumericField("NewPrice", "New Price"), "Price"); | ||
| 68 |         $fields->addFieldToTab("Root.Details", new ReadOnlyField("Savings", "Savings", $this->getPrice() - $this->NewPrice), "Price"); | ||
| 69 | return $fields; | ||
| 70 | } | ||
| 71 | |||
| 72 | |||
| 73 | /** | ||
| 74 | *@return TreeMultiselectField | ||
| 75 | **/ | ||
| 76 | protected function getIncludedProductsFormField() | ||
| 77 |     { | ||
| 78 | $field = new TreeMultiselectField( | ||
| 79 | $name = "IncludedProducts", | ||
| 80 | $title = "Included Products", | ||
| 81 | $sourceObject = "SiteTree", | ||
| 82 | $keyField = "ID", | ||
| 83 | $labelField = "MenuTitle" | ||
| 84 | ); | ||
| 85 |         $filter = create_function('$obj', 'return ( ( $obj InstanceOf Product || $obj InstanceOf ProductGroup) && ($obj->ID != '.$this->ID.'));'); | ||
| 0 ignored issues–
                            show The use of  create_functionis highly discouraged, better use a closure.
 // Instead of
$function = create_function('$a, $b', 'return $a + $b');
// Better use
$function = function($a, $b) { return $a + $b; }
 Loading history... | |||
| 86 | $field->setFilterFunction($filter); | ||
| 87 | return $field; | ||
| 88 | } | ||
| 89 | |||
| 90 | /** | ||
| 91 | * Conditions for whether a product can be purchased. | ||
| 92 | * | ||
| 93 | * If it has the checkbox for 'Allow this product to be purchased', | ||
| 94 | * as well as having a price, it can be purchased. Otherwise a user | ||
| 95 | * can't buy it. | ||
| 96 | * | ||
| 97 | * Other conditions may be added by decorating with the canPurcahse function | ||
| 98 | * | ||
| 99 | * @return boolean | ||
| 100 | */ | ||
| 101 | public function canPurchase(Member $member = null, $checkPrice = true) | ||
| 102 |     { | ||
| 103 |         if ($includedProducts = $this->IncludedProducts()) { | ||
| 104 |             if ($includedProducts->count()) { | ||
| 105 |                 foreach ($includedProducts as $includedProduct) { | ||
| 106 |                     if (!$includedProduct->canPurchase($member)) { | ||
| 107 | return false; | ||
| 108 | } | ||
| 109 | } | ||
| 110 | return parent::canPurchase($member); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | return false; | ||
| 114 | } | ||
| 115 | |||
| 116 | public function classNameForOrderItem() | ||
| 117 |     { | ||
| 118 | return "CombinationProduct_OrderItem"; | ||
| 119 | } | ||
| 120 | |||
| 121 | |||
| 122 | /** | ||
| 123 | * | ||
| 124 | * | ||
| 125 | * | ||
| 126 | */ | ||
| 127 | public function getPrice() | ||
| 128 |     { | ||
| 129 |         if ($includedProducts = $this->IncludedProducts()) { | ||
| 130 | $originalPrice = 0; | ||
| 131 |             if ($includedProducts && $includedProducts->count()) { | ||
| 132 |                 foreach ($includedProducts as $includedProduct) { | ||
| 133 | $originalPrice += $includedProduct->CalculatedPrice(); | ||
| 134 | } | ||
| 135 | } | ||
| 136 | } | ||
| 137 | return $originalPrice; | ||
| 0 ignored issues–
                            show The variable  $originalPricedoes not seem to be defined for all execution paths leading up to this point.If you define a variable conditionally, it can happen that it is not defined for all execution paths. Let’s take a look at an example: function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;
        case 'bar':
            $x = 2;
            break;
    }
    // $x is potentially undefined here.
    echo $x;
}
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined. Available Fixes
  Loading history... | |||
| 138 | } | ||
| 139 | |||
| 140 | public function getCalculatedPrice() | ||
| 0 ignored issues–
                            show The return type could not be reliably inferred; please add a  @returnannotation.Our type inference engine in quite powerful, but sometimes the code does not
provide enough clues to go by. In these cases we request you to add a   Loading history... | |||
| 141 |     { | ||
| 142 |         return $this->getField("NewPrice"); | ||
| 143 | } | ||
| 144 | |||
| 145 | /** | ||
| 146 | * remove any non-products from the list. | ||
| 147 | * | ||
| 148 | */ | ||
| 149 | public function onBeforeWrite() | ||
| 150 |     { | ||
| 151 | parent::onBeforeWrite(); | ||
| 152 | $includedProducts = $this->IncludedProducts(); | ||
| 153 |         if ($includedProducts) { | ||
| 154 |             foreach ($includedProducts as $includedProduct) { | ||
| 155 |                 if (!$includedProduct instanceof Product) { | ||
| 156 | $includedProducts->remove($includedProduct); | ||
| 157 | } | ||
| 158 | } | ||
| 159 | } | ||
| 160 | $this->Price = $this->NewPrice; | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | class CombinationProduct_Controller extends Product_Controller | ||
| 0 ignored issues–
                            show PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.
                                             You can fix this by adding a namespace to your class: namespace YourVendor;
class YourClass { }
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.  Loading history... | |||
| 165 | { | ||
| 166 | public function init() | ||
| 167 |     { | ||
| 168 | parent::init(); | ||
| 169 |         Requirements::themedCSS("CombinationProduct", "ecommerce_combo_product"); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | |||
| 174 | class CombinationProduct_OrderItem extends Product_OrderItem | ||
| 0 ignored issues–
                            show PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.
                                             You can fix this by adding a namespace to your class: namespace YourVendor;
class YourClass { }
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.  Loading history... | |||
| 175 | { | ||
| 176 | |||
| 177 | |||
| 178 | //add a deletion system | ||
| 179 | |||
| 180 | public function onBeforeDelete() | ||
| 181 |     { | ||
| 182 | parent::onBeforeDelete(); | ||
| 183 | $includedProductsOrderItems = IncludedProduct_OrderItem::get() | ||
| 184 |             ->filter(array("ParentOrderItemID" => $this->ID, "OrderID" => $this->Order()->ID)); | ||
| 185 |         if ($includedProductsOrderItems->count()) { | ||
| 186 |             foreach ($includedProductsOrderItems as $includedProductsOrderItem) { | ||
| 187 | $includedProductsOrderItem->delete(); | ||
| 188 | $includedProductsOrderItem->destroy(); | ||
| 189 | } | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | public function TableSubTitle() | ||
| 194 |     { | ||
| 195 | $buyable = $this->Buyable(); | ||
| 196 | $includedProducts = $buyable->IncludedProducts(); | ||
| 197 | $titleArray = array(); | ||
| 198 |         if ($includedProducts) { | ||
| 199 |             foreach ($includedProducts as $includedProduct) { | ||
| 200 | $titleArray[] = $includedProduct->MenuTitle; | ||
| 201 | } | ||
| 202 | } | ||
| 203 |         if (count($titleArray)) { | ||
| 204 |             return _t("CombinationProduct.INCLUDES", "Includes").": ".implode(", ", $titleArray)."."; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | public function onBeforeWrite() | ||
| 209 |     { | ||
| 210 | parent::onBeforeWrite(); | ||
| 211 | $this->Sort = $this->Buyable()->ID; | ||
| 0 ignored issues–
                            show The property  Sortdoes not exist onobject<CombinationProduct_OrderItem>. Since you implemented__set, maybe consider adding a @property annotation.Since your code implements the magic setter  <?php
/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;
    private $allowedProperties = array('x', 'y', 'text');
    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }
    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }
}
Since the property has write access only, you can use the @property-write annotation instead. Of course, you may also just have mistyped another name, in which case you should fix the error. See also the PhpDoc documentation for @property.  Loading history... | |||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | class IncludedProduct_OrderItem extends Product_OrderItem | ||
| 0 ignored issues–
                            show PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.
                                             You can fix this by adding a namespace to your class: namespace YourVendor;
class YourClass { }
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.  Loading history... | |||
| 216 | { | ||
| 217 | private static $has_one = array( | ||
| 0 ignored issues–
                            show | |||
| 218 | "ParentOrderItem" => "CombinationProduct_OrderItem" | ||
| 219 | ); | ||
| 220 | |||
| 221 | public function LiveCalculatedTotal() | ||
| 222 |     { | ||
| 223 | return 0; | ||
| 224 | } | ||
| 225 | |||
| 226 | public function Total($recalculate = false) | ||
| 227 |     { | ||
| 228 | return $this->getTotal($recalculate); | ||
| 229 | } | ||
| 230 | |||
| 231 | public function getTotal($recalculate = false) | ||
| 232 |     { | ||
| 233 | return 0; | ||
| 234 | } | ||
| 235 | |||
| 236 | public function TableSubTitle() | ||
| 237 |     { | ||
| 238 |         Requirements::themedCSS("CombinationProductModifier", "ecommerce_combo_product"); | ||
| 239 |         return _t("CombinationProduct.PARTOF", "Part of").": ".$this->ParentOrderItem()->TableTitle()."."; | ||
| 0 ignored issues–
                            show The method  ParentOrderItemdoes not exist onobject<IncludedProduct_OrderItem>? Since you implemented__call, maybe consider adding a @method annotation.If you implement  This is often the case, when  class ParentClass {
    private $data = array();
    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }
        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}
/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
 Loading history... | |||
| 240 | } | ||
| 241 | |||
| 242 | public function onBeforeWrite() | ||
| 243 |     { | ||
| 244 | parent::onBeforeWrite(); | ||
| 245 |         if ($parentOrderItem = $this->ParentOrderItem()) { | ||
| 0 ignored issues–
                            show The method  ParentOrderItemdoes not exist onobject<IncludedProduct_OrderItem>? Since you implemented__call, maybe consider adding a @method annotation.If you implement  This is often the case, when  class ParentClass {
    private $data = array();
    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }
        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}
/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
 Loading history... | |||
| 246 |             if ($buyable = $parentOrderItem->Buyable()) { | ||
| 247 | $this->Sort = $buyable->ID + 1; | ||
| 0 ignored issues–
                            show The property  Sortdoes not exist onobject<IncludedProduct_OrderItem>. Since you implemented__set, maybe consider adding a @property annotation.Since your code implements the magic setter  <?php
/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;
    private $allowedProperties = array('x', 'y', 'text');
    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }
    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }
}
Since the property has write access only, you can use the @property-write annotation instead. Of course, you may also just have mistyped another name, in which case you should fix the error. See also the PhpDoc documentation for @property.  Loading history... | |||
| 248 | } | ||
| 249 | } | ||
| 250 | } | ||
| 251 | |||
| 252 | public function RemoveLink() | ||
| 253 |     { | ||
| 254 | return ""; | ||
| 255 | } | ||
| 256 | |||
| 257 | |||
| 258 | public function RemoveAllLink() | ||
| 259 |     { | ||
| 260 | return ""; | ||
| 261 | } | ||
| 262 | |||
| 263 | public function QuantityField() | ||
| 264 |     { | ||
| 265 |         return new ReadonlyField("Quantity", "", $this->Quantity); | ||
| 0 ignored issues–
                            show The property  Quantitydoes not exist onobject<IncludedProduct_OrderItem>. Since you implemented__get, maybe consider adding a @property annotation.Since your code implements the magic getter  <?php
/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;
    private $allowedProperties = array('x', 'y', 'text');
    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }
    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }
}
If the property has read access only, you can use the @property-read annotation instead. Of course, you may also just have mistyped another name, in which case you should fix the error. See also the PhpDoc documentation for @property.  Loading history... | |||
| 266 | } | ||
| 267 | |||
| 268 | |||
| 269 | public function onBeforeDelete() | ||
| 270 |     { | ||
| 271 | parent::onBeforeDelete(); | ||
| 272 | CartResponse::set_force_reload(); | ||
| 273 | } | ||
| 274 | } | ||
| 275 | 
 
                                
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.