Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
tool_asset_sorter.inc
1 <?php
17 require_once SQ_SYSTEM_ROOT.'/core/assets/system/tool/tool.inc';
18 
32 class Tool_Asset_Sorter extends Tool
33 {
34 
35 
42  function Tool_Asset_Sorter($assetid=0)
43  {
44  $this->Tool($assetid);
45 
46  }//end constructor
47 
48 
59  public static function paintTool(&$o, $type_code)
60  {
61  $parent_asset_id = 0;
62  $sort_type = 'sort_by_field';
63  $sort_field = '';
64  $selected_asset_type = '';
65  $selected_asset_attribute = '';
66  $sort_direction = '';
67 
68  if (isset($_POST[$type_code.'_parent']['assetid'])) {
69  $parent_asset_id = (int) $_POST[$type_code.'_parent']['assetid'];
70  }
71 
72  if (isset($_POST[$type_code.'_sort_type'])) {
73  $sort_type = addslashes($_POST[$type_code.'_sort_type']);
74  }
75 
76  if (isset($_POST[$type_code.'_field'])) {
77  $sort_field = addslashes($_POST[$type_code.'_field']);
78  }
79 
80  if (isset($_POST[$type_code.'_asset_type'])) {
81  $selected_asset_type = addslashes($_POST[$type_code.'_asset_type']);
82  }
83 
84  if (isset($_POST[$type_code.'_asset_attribute'])) {
85  $selected_asset_attribute = addslashes($_POST[$type_code.'_asset_attribute']);
86  }
87 
88  if (isset($_POST[$type_code.'_direction'])) {
89  $sort_direction = addslashes($_POST[$type_code.'_direction']);
90  }
91 
92  $sort_by_field = ($sort_type == 'sort_by_field');
93 
94  $o->openSection(translate('tool_asset_sorter_sort_options'));
95  $o->openField(translate('parent'));
96  asset_finder($type_code.'_parent', $parent_asset_id);
97  $o->closeField();
98  $o->openField(translate('tool_asset_sorter_sort_type'));
99  $options = Array(
100  'sort_by_field' => 'Asset Field',
101  'sort_by_attribute' => 'Asset Attribute'
102  );
103  combo_box($type_code.'_sort_type', $options, FALSE, $sort_type, 0, 'onchange="setFieldType(this);"');
104  $o->closeField();
105  $o->openField(translate('tool_asset_sorter_sort_field'), '', '', !$sort_by_field, 'sort_field');
106  $options = $GLOBALS['SQ_SYSTEM']->am->getAssetInfoFields();
107  // unset some things by which we can't or don't want to sort properly
108  unset($options['force_secure']);
109  unset($options['version']);
110  unset($options['charset']);
111  unset($options['languages']);
112  combo_box($type_code.'_field', $options, FALSE, $sort_field);
113  $o->closeField();
114  $o->openField(translate('asset_type'), '', '', $sort_by_field, 'sort_attr_asset');
115  hidden_field($type_code.'_asset_type_old', $selected_asset_type);
116  asset_type_chooser($type_code.'_asset_type', FALSE, Array($selected_asset_type), TRUE);
117  $o->closeField();
118  $o->openField(translate('tool_asset_sorter_sort_attribute'), '', '', $sort_by_field, 'sort_attr_attr');
119  if ($selected_asset_type == '') {
120  echo '<b>['.translate('asset_type_not_selected').']</b>';
121  } else {
122  $attrs = $GLOBALS['SQ_SYSTEM']->am->getAssetTypeAttributes($selected_asset_type);
123  $options = Array();
124 
125  foreach ($attrs as $attr_name => $val) {
126  $options[$attr_name] = $attr_name;
127  }
128 
129  asort($options);
130  combo_box($type_code.'_asset_attribute', $options, FALSE, $selected_asset_attribute);
131  }
132  $o->closeField();
133  $o->openField(translate('tool_asset_sorter_sort_direction'));
134  combo_box($type_code.'_direction', Array('ASC' => 'Ascending', 'DESC' => 'Descending'), FALSE, $sort_direction);
135  $o->closeField();
136  $o->openField(translate('note'));
137  $o->note(translate('tool_asset_sorter_usage_notes'));
138  $o->closeField();
139 
140  $o->addJsInclude(sq_web_path('lib').'/js/tool_asset_sorter.js');
141 
142  $o->closeSection();
143 
144  }//end paintTool()
145 
146 
157  public static function processTool(&$o, $type_code)
158  {
159  $parentid = $_REQUEST[$type_code.'_parent']['assetid'];
160  if (empty($parentid)) return FALSE;
161  $parent = $GLOBALS['SQ_SYSTEM']->am->getAsset($parentid);
162  if (is_null($parent)) return FALSE;
163 
164  $sort_type = $_POST[$type_code.'_sort_type'];
165 
166  $sort_field = $_REQUEST[$type_code.'_field'];
167  $sortable_fields = $GLOBALS['SQ_SYSTEM']->am->getAssetInfoFields();
168 
169  // Ensure that the user hasn't changed the asset type to avoid asset type + asset attribute mismatches
170  $sort_asset_type = $_POST[$type_code.'_asset_type'];
171  $old_sort_asset_type = $_POST[$type_code.'_asset_type_old'];
172  if (($sort_type == 'sort_by_attribute') && ($sort_asset_type != $old_sort_asset_type)) {
173  return FALSE;
174  }
175 
176  $sort_asset_attribute = '';
177  if (($sort_type == 'sort_by_attribute') && empty($sort_asset_type)) {
178  return FALSE;
179  }
180 
181  if (isset($_POST[$type_code.'_asset_attribute'])) {
182  $sort_asset_attribute = $_POST[$type_code.'_asset_attribute'];
183  }
184 
185 
186  $direction = $_REQUEST[$type_code.'_direction'];
187  $direction = (strtolower($direction) == 'asc') ? 'ASC' : 'DESC';
188 
189  // sanitize parameters for SQL injection attack
190  if(!empty($sort_field) && !preg_match('/^[a-z_]+$/i', $sort_field))
191  return FALSE;
192  if(!empty($sort_asset_type) && !preg_match('/^[a-z_]+$/i', $sort_asset_type))
193  return FALSE;
194 
195 
196  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
197 
198  // Counter to track maximum sort order of assets sorted so far
199  $index_delta = 0;
200 
201  // There is a limit to CASE statement size in Oracle, that limits it to
202  // 127 WHEN-THEN pairs (in theory), so limit to 127 at a time on Oracle
203  $chunk_size = (MatrixDAL::getDbType() == 'oci') ? 127 : 500;
204 
205  if ($sort_type == 'sort_by_attribute') {
206  // Sort the specified asset type only, by Asset Attribute
207  $sql = 'SELECT l.linkid
208  FROM sq_ast a, sq_ast_attr t, sq_ast_attr_val v, sq_ast_lnk l
209  WHERE majorid = :parentid
210  AND link_type IN (:link_type_1, :link_type_2)
211  AND a.assetid = l.minorid
212  AND a.type_code = :sort_asset_type
213  AND t.name = :sort_asset_attr
214  AND a.assetid = v.assetid
215  AND t.attrid = v.attrid ';
216 
217  // Special CLOB handling for Oracle
218  if (MatrixDAL::getDbType() === 'oci') {
219  $sql .= 'ORDER BY TO_CHAR(v.custom_val) '.$direction;
220  } else {
221  $sql .= 'ORDER BY v.custom_val '.$direction;
222  }
223 
224  try {
225  $query = MatrixDAL::preparePdoQuery($sql);
226  MatrixDAL::bindValueToPdo($query, 'parentid', $parentid);
227  MatrixDAL::bindValueToPdo($query, 'link_type_1', SQ_LINK_TYPE_1);
228  MatrixDAL::bindValueToPdo($query, 'link_type_2', SQ_LINK_TYPE_2);
229  MatrixDAL::bindValueToPdo($query, 'sort_asset_type', $sort_asset_type);
230  MatrixDAL::bindValueToPdo($query, 'sort_asset_attr', $sort_asset_attribute);
231  $linkids = MatrixDAL::executePdoAssoc($query, 0);
232  } catch (Exception $e) {
233  throw new Exception('Unable to get link ids for parent: '.$parentid.' due to database error: '.$e->getMessage());
234  }
235 
236  // Divide-and-conquer the query, keeping track of how many assets that have been sorted so far (this is important later on)
237  Tool_Asset_Sorter::_chunkQuery($linkids, $chunk_size);
238  $index_delta = count($linkids);
239 
240  // Prepare a query for slotting in the remaining assets (ie; those that are not of the selected asset type)
241  $sql = 'SELECT l.linkid
242  FROM sq_ast_lnk l
243  JOIN sq_ast a ON l.minorid = a.assetid
244  WHERE majorid = :parentid
245  AND link_type IN (:link_type_1, :link_type_2)
246  AND a.type_code != '.MatrixDAL::quote($sort_asset_type).'
247  ORDER BY l.sort_order';
248  } else {
249  // Sort by Asset Field
250  $sql = 'SELECT l.linkid
251  FROM sq_ast_lnk l
252  JOIN sq_ast a ON l.minorid = a.assetid
253  WHERE majorid = :parentid
254  AND link_type IN (:link_type_1, :link_type_2)
255  ORDER BY a.'.$sort_field.' '.$direction.', a.assetid DESC';
256  }
257 
258  try {
259  $query = MatrixDAL::preparePdoQuery($sql);
260  MatrixDAL::bindValueToPdo($query, 'parentid', $parentid);
261  MatrixDAL::bindValueToPdo($query, 'link_type_1', SQ_LINK_TYPE_1);
262  MatrixDAL::bindValueToPdo($query, 'link_type_2', SQ_LINK_TYPE_2);
263  $linkids = MatrixDAL::executePdoAssoc($query, 0);
264  } catch (Exception $e) {
265  throw new Exception('Unable to get link ids for parent: '.$parentid.' due to database error: '.$e->getMessage());
266  }
267 
268  // Chunkify SQL queries taking into account the maximum sort order any assets that may have been sorted above.
269  // This ensures that the resultant assets are represented with ascending sort order and without duplicated sort orders
270  Tool_Asset_Sorter::_chunkQuery($linkids, $chunk_size, $index_delta);
271 
272  // Do DB-related magic
273  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
274 
275  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
276  $em->broadcastEvent($parent, 'AssetUpdate', Array());
277 
278  $o->openSection('Asset Sorting Complete');
279  $o->openRaw();
280  if ($sort_type == 'sort_by_attribute') {
281  $asset_types = $GLOBALS['SQ_SYSTEM']->am->getAssetTypes();
282  $asset_type_name = $asset_types[$sort_asset_type]['name'];
283  echo $asset_type_name.' child assets of '.get_asset_tag_line($parentid).' have been sorted by '.$sort_asset_attribute;
284  } else {
285  echo 'The children of '.get_asset_tag_line($parentid).' have been sorted by '.$sortable_fields[$sort_field];
286  }
287  $o->closeRaw();
288  $o->closeSection();
289  return TRUE;
290 
291  }//end processTool()
292 
293 
306  private static function _chunkQuery($linkids, $chunk_size, $index_delta = 0)
307  {
308  foreach (array_chunk($linkids, $chunk_size, TRUE) as $chunk) {
309  $cases = '';
310  foreach ($chunk as $i => $linkid) {
311  $cases .= 'WHEN (linkid = '.$linkid.') THEN '.($i+$index_delta).' ';
312  }
313  $sql = 'UPDATE sq_ast_lnk
314  SET sort_order = CASE '.$cases.' ELSE sort_order END
315  WHERE linkid IN ('.implode(', ', $chunk).')';
316  try {
317  $res = MatrixDAL::executeSql($sql);
318  } catch (Exception $e) {
319  throw new Exception('Unable to update link table with new sort order due to database error: '.$e->getMessage());
320  }
321  }
322  }//end _chunkQuery()
323 
324 
325 }//end class
326 ?>