1<?php 2/** 3 * Copyright (c) 2010-2018 Combodo SARL 4 * 5 * This file is part of iTop. 6 * 7 * iTop is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU Affero General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 * 12 * iTop is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with iTop. If not, see <http://www.gnu.org/licenses/> 19 * 20 */ 21 22/** 23 * Created by PhpStorm. 24 * Date: 24/08/2018 25 * Time: 14:35 26 */ 27class ormSet 28{ 29 protected $sClass; // class of the field 30 protected $sAttCode; // attcode of the field 31 protected $aOriginalObjects = null; 32 33 /** 34 * Object from the original set, minus the removed objects 35 */ 36 protected $aPreserved = array(); 37 38 /** 39 * New items 40 */ 41 protected $aAdded = array(); 42 43 /** 44 * Removed items 45 */ 46 protected $aRemoved = array(); 47 48 /** 49 * Modified items (mass edit) 50 */ 51 protected $aModified = array(); 52 53 /** 54 * @var int Max number of tags in collection 55 */ 56 protected $iLimit; 57 58 /** 59 * __toString magical function overload. 60 */ 61 public function __toString() 62 { 63 $aValue = $this->GetValues(); 64 if (!empty($aValue)) 65 { 66 return implode(', ', $aValue); 67 } 68 else 69 { 70 return ' '; 71 } 72 } 73 74 /** 75 * ormSet constructor. 76 * 77 * @param string $sClass 78 * @param string $sAttCode 79 * @param int $iLimit 80 * 81 * @throws \Exception 82 */ 83 public function __construct($sClass, $sAttCode, $iLimit = 12) 84 { 85 $this->sAttCode = $sAttCode; 86 87 $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); 88 if (!$oAttDef instanceof AttributeSet) 89 { 90 throw new Exception("ormSet: field {$sClass}:{$sAttCode} is not a set"); 91 } 92 $this->sClass = $sClass; 93 $this->iLimit = $iLimit; 94 } 95 96 /** 97 * @return string 98 */ 99 public function GetClass() 100 { 101 return $this->sClass; 102 } 103 104 /** 105 * @return string 106 */ 107 public function GetAttCode() 108 { 109 return $this->sAttCode; 110 } 111 112 /** 113 * 114 * @param array $aItems 115 * 116 * @throws \CoreException 117 * @throws \CoreUnexpectedValue when a code is invalid 118 */ 119 public function SetValues($aItems) 120 { 121 if (!is_array($aItems)) 122 { 123 throw new CoreUnexpectedValue("Wrong value {$aItems} for {$this->sClass}:{$this->sAttCode}"); 124 } 125 126 $aValues = array(); 127 $iCount = 0; 128 $bError = false; 129 foreach($aItems as $oItem) 130 { 131 $iCount++; 132 if (($this->iLimit != 0) && ($iCount > $this->iLimit)) 133 { 134 $bError = true; 135 continue; 136 } 137 $aValues[] = $oItem; 138 } 139 140 $this->aPreserved = &$aValues; 141 $this->aRemoved = array(); 142 $this->aAdded = array(); 143 $this->aModified = array(); 144 $this->aOriginalObjects = $aValues; 145 146 if ($bError) 147 { 148 throw new CoreException("Maximum number of items ({$this->iLimit}) reached for {$this->sClass}:{$this->sAttCode}"); 149 } 150 } 151 152 public function Count() 153 { 154 return count($this->aPreserved) + count($this->aAdded) - count($this->aRemoved); 155 } 156 157 /** 158 * @return array of codes 159 */ 160 public function GetValues() 161 { 162 $aValues = array_merge($this->aPreserved, $this->aAdded); 163 164 return $aValues; 165 } 166 167 /** 168 * @return array of tag labels indexed by code for only the added tags 169 */ 170 private function GetAdded() 171 { 172 return $this->aAdded; 173 } 174 175 /** 176 * @return array of tag labels indexed by code for only the removed tags 177 */ 178 private function GetRemoved() 179 { 180 return $this->aRemoved; 181 } 182 183 /** Get the delta with another ItemSet 184 * 185 * $aDelta['added] = array of tag codes for only the added tags 186 * $aDelta['removed'] = array of tag codes for only the removed tags 187 * 188 * @param \ormSet $oOtherSet 189 * 190 * @return array 191 * 192 * @throws \CoreException 193 * @throws \CoreUnexpectedValue 194 * @throws \Exception 195 */ 196 public function GetDelta(ormSet $oOtherSet) 197 { 198 $oSet = new ormSet($this->sClass, $this->sAttCode, $this->iLimit); 199 // Set the initial value 200 $aOrigItems = $this->GetValues(); 201 $oSet->SetValues($aOrigItems); 202 203 // now remove everything 204 foreach($aOrigItems as $oItem) 205 { 206 $oSet->Remove($oItem); 207 } 208 209 // now add the tags of the other ItemSet 210 foreach($oOtherSet->GetValues() as $oItem) 211 { 212 $oSet->Add($oItem); 213 } 214 215 $aDelta = array(); 216 $aDelta['added'] = $oSet->GetAdded(); 217 $aDelta['removed'] = $oSet->GetRemoved(); 218 219 return $aDelta; 220 } 221 222 /** 223 * @return string[] list of codes for partial entries 224 */ 225 public function GetModified() 226 { 227 return $this->aModified; 228 } 229 230 /** 231 * Apply a delta to the current ItemSet 232 * $aDelta['added] = array of added items 233 * $aDelta['removed'] = array of removed items 234 * 235 * @param $aDelta 236 * 237 * @throws \CoreException 238 */ 239 public function ApplyDelta($aDelta) 240 { 241 if (isset($aDelta['removed'])) 242 { 243 foreach($aDelta['removed'] as $oItem) 244 { 245 $this->Remove($oItem); 246 } 247 } 248 if (isset($aDelta['added'])) 249 { 250 foreach($aDelta['added'] as $oItem) 251 { 252 $this->Add($oItem); 253 } 254 } 255 256 // Reset the object 257 $this->SetValues($this->GetValues()); 258 } 259 260 /** 261 * @param string $oItem 262 * 263 * @throws \CoreException 264 */ 265 public function Add($oItem) 266 { 267 if (($this->iLimit != 0) && ($this->Count() > $this->iLimit)) 268 { 269 throw new CoreException("Maximum number of items ({$this->iLimit}) reached for {$this->sClass}:{$this->sAttCode}"); 270 } 271 if ($this->IsItemInList($this->aPreserved, $oItem) || $this->IsItemInList($this->aAdded, $oItem)) 272 { 273 // nothing to do, already existing tag 274 return; 275 } 276 // if removed and added again 277 if (($this->RemoveItemFromList($this->aRemoved, $oItem)) !== false) 278 { 279 // put it back into preserved 280 $this->aPreserved[] = $oItem; 281 // no need to add it to aModified : was already done when calling RemoveItem method 282 } 283 else 284 { 285 $this->aAdded[] = $oItem; 286 $this->aModified[] = $oItem; 287 } 288 } 289 290 /** 291 * @param $oItem 292 */ 293 public function Remove($oItem) 294 { 295 if ($this->IsItemInList($this->aRemoved, $oItem)) 296 { 297 // nothing to do, already removed tag 298 return; 299 } 300 301 if ($this->RemoveItemFromList($this->aAdded, $oItem) !== false) 302 { 303 $this->aModified[] = $oItem; 304 305 return; // if present in added, can't be in preserved ! 306 } 307 308 if ($this->RemoveItemFromList($this->aPreserved, $oItem) !== false) 309 { 310 $this->aModified[] = $oItem; 311 $this->aRemoved[] = $oItem; 312 } 313 } 314 315 private function IsItemInList($aItemList, $oItem) 316 { 317 return in_array($oItem, $aItemList); 318 } 319 320 /** 321 * @param \DBObject[] $aItemList 322 * @param $oItem 323 * 324 * @return bool|\DBObject false if not found, else the removed element 325 */ 326 private function RemoveItemFromList(&$aItemList, $oItem) 327 { 328 if (!($this->IsItemInList($aItemList, $oItem))) 329 { 330 return false; 331 } 332 foreach ($aItemList as $index => $value) 333 { 334 if ($value === $oItem) 335 { 336 unset($aItemList[$index]); 337 return $oItem; 338 } 339 } 340 341 return false; 342 } 343 344 /** 345 * Populates the added and removed arrays for bulk edit 346 * 347 * @param string[] $aItems 348 * 349 * @throws \CoreException 350 */ 351 public function GenerateDiffFromArray($aItems) 352 { 353 foreach($this->GetValues() as $oCurrentItem) 354 { 355 if (!in_array($oCurrentItem, $aItems)) 356 { 357 $this->Remove($oCurrentItem); 358 } 359 } 360 361 foreach($aItems as $oNewItem) 362 { 363 $this->Add($oNewItem); 364 } 365 } 366 367 /** 368 * Compare Item Set 369 * 370 * @param \ormSet $other 371 * 372 * @return bool true if same tag set 373 */ 374 public function Equals(ormSet $other) 375 { 376 return implode(', ', $this->GetValue()) === implode(', ', $other->GetValue()); 377 } 378 379 380}