MediaWiki  master
categoryChangesAsRdf.php
Go to the documentation of this file.
1 <?php
22 
23 require_once __DIR__ . '/Maintenance.php';
24 
35  const SPARQL_INSERT = <<<SPARQL
36 INSERT DATA {
37 %s
38 };
39 
41 
46 DELETE {
47 ?category ?x ?y
48 } WHERE {
49  VALUES ?category {
50  %s
51  }
52 };
53 
55 
60 DELETE {
61 ?category ?x ?y
62 } INSERT {
63 %s
64 } WHERE {
65  VALUES ?category {
66  %s
67  }
68 };
69 
71 
75  private $rdfWriter;
80  private $categoriesRdf;
81 
82  private $startTS;
83  private $endTS;
84 
90  protected $processed = [];
91 
92  public function __construct() {
93  parent::__construct();
94 
95  $this->addDescription( "Generate RDF dump of category changes in a wiki." );
96 
97  $this->setBatchSize( 200 );
98  $this->addOption( 'output', "Output file (default is stdout). Will be overwritten.", false,
99  true, 'o' );
100  $this->addOption( 'start', 'Starting timestamp (inclusive), in ISO or Mediawiki format.',
101  true, true, 's' );
102  $this->addOption( 'end', 'Ending timestamp (exclusive), in ISO or Mediawiki format.', true,
103  true, 'e' );
104  }
105 
109  public function initialize() {
110  // SPARQL Update syntax is close to Turtle format, so we can use Turtle writer.
111  $this->rdfWriter = new TurtleRdfWriter();
112  $this->categoriesRdf = new CategoriesRdf( $this->rdfWriter );
113  }
114 
115  public function execute() {
116  global $wgRCMaxAge;
117 
118  $this->initialize();
119  $startTS = new MWTimestamp( $this->getOption( "start" ) );
120 
121  $endTS = new MWTimestamp( $this->getOption( "end" ) );
122  $now = new MWTimestamp();
123 
124  if ( $now->getTimestamp() - $startTS->getTimestamp() > $wgRCMaxAge ) {
125  $this->error( "Start timestamp too old, maximum RC age is $wgRCMaxAge!" );
126  }
127  if ( $now->getTimestamp() - $endTS->getTimestamp() > $wgRCMaxAge ) {
128  $this->error( "End timestamp too old, maximum RC age is $wgRCMaxAge!" );
129  }
130 
131  $this->startTS = $startTS->getTimestamp();
132  $this->endTS = $endTS->getTimestamp();
133 
134  $outFile = $this->getOption( 'output', 'php://stdout' );
135  if ( $outFile === '-' ) {
136  $outFile = 'php://stdout';
137  }
138 
139  $output = fopen( $outFile, 'wb' );
140 
141  $this->categoriesRdf->setupPrefixes();
142  $this->rdfWriter->start();
143 
144  $prefixes = $this->getRdf();
145  // We have to strip @ from prefix, since SPARQL UPDATE doesn't use them
146  // Also strip dot at the end.
147  $prefixes = preg_replace( [ '/^@/m', '/\s*[.]$/m' ], '', $prefixes );
148  fwrite( $output, $prefixes );
149 
150  $dbr = $this->getDB( DB_REPLICA, [ 'vslow' ] );
151 
152  // Deletes go first because if the page was deleted, other changes
153  // do not matter. This only gets true deletes, i.e. not pages that were restored.
154  $this->handleDeletes( $dbr, $output );
155  // Moves go before additions because if category is moved, we should not process creation
156  // as it would produce wrong data - because create row has old title
157  $this->handleMoves( $dbr, $output );
158  // We need to handle restores too since delete may have happened in previous update.
159  $this->handleRestores( $dbr, $output );
160  // Process newly added pages
161  $this->handleAdds( $dbr, $output );
162  // Process page edits
163  $this->handleEdits( $dbr, $output );
164  // Process categorization changes
165  $this->handleCategorization( $dbr, $output );
166 
167  // Update timestamp
168  fwrite( $output, $this->updateTS( $this->endTS ) );
169  }
170 
175  private function getInsertRdf() {
176  $rdfText = $this->getRdf();
177  if ( !$rdfText ) {
178  return "";
179  }
180  return sprintf( self::SPARQL_INSERT, $rdfText );
181  }
182 
191  private function getCategoriesUpdate( IDatabase $dbr, $deleteUrls, $pages, $mark ) {
192  if ( empty( $deleteUrls ) ) {
193  return "";
194  }
195 
196  if ( !empty( $pages ) ) {
197  $this->writeParentCategories( $dbr, $pages );
198  }
199 
200  return "# $mark\n" . sprintf( self::SPARQL_DELETE, implode( ' ', $deleteUrls ) ) .
201  $this->getInsertRdf();
202  }
203 
210  private function writeParentCategories( IDatabase $dbr, $pages ) {
211  foreach ( $this->getCategoryLinksIterator( $dbr, array_keys( $pages ) ) as $row ) {
212  $this->categoriesRdf->writeCategoryLinkData( $pages[$row->cl_from], $row->cl_to );
213  }
214  }
215 
221  public function updateTS( $timestamp ) {
222  $dumpUrl = '<' . $this->categoriesRdf->getDumpURI() . '>';
223  $ts = wfTimestamp( TS_ISO_8601, $timestamp );
224  $tsQuery = <<<SPARQL
225 DELETE {
226  $dumpUrl schema:dateModified ?o .
227 }
228 WHERE {
229  $dumpUrl schema:dateModified ?o .
230 };
231 INSERT DATA {
232  $dumpUrl schema:dateModified "$ts"^^xsd:dateTime .
233 }
234 
235 SPARQL;
236  return $tsQuery;
237  }
238 
246  private function setupChangesIterator(
247  IDatabase $dbr,
248  array $columns = [],
249  array $extra_tables = []
250  ) {
251  $tables = [ 'recentchanges', 'page_props', 'category' ];
252  if ( $extra_tables ) {
253  $tables = array_merge( $tables, $extra_tables );
254  }
255  $it = new BatchRowIterator( $dbr,
256  $tables,
257  [ 'rc_timestamp' ],
258  $this->mBatchSize
259  );
260  $this->addTimestampConditions( $it, $dbr );
261  $it->addJoinConditions(
262  [
263  'page_props' => [
264  'LEFT JOIN', [ 'pp_propname' => 'hiddencat', 'pp_page = rc_cur_id' ]
265  ],
266  'category' => [
267  'LEFT JOIN', [ 'cat_title = rc_title' ]
268  ]
269  ]
270  );
271  $it->setFetchColumns( array_merge( $columns, [
272  'rc_title',
273  'rc_cur_id',
274  'pp_propname',
275  'cat_pages',
276  'cat_subcats',
277  'cat_files'
278  ] ) );
279  return $it;
280  }
281 
287  protected function getNewCatsIterator( IDatabase $dbr ) {
288  $it = $this->setupChangesIterator( $dbr );
289  $it->addConditions( [
290  'rc_namespace' => NS_CATEGORY,
291  'rc_new' => 1,
292  ] );
293  return $it;
294  }
295 
301  protected function getMovedCatsIterator( IDatabase $dbr ) {
302  $it = $this->setupChangesIterator( $dbr, [ 'page_title', 'page_namespace' ], [ 'page' ] );
303  $it->addConditions( [
304  'rc_namespace' => NS_CATEGORY,
305  'rc_new' => 0,
306  'rc_log_type' => 'move',
307  'rc_type' => RC_LOG,
308  ] );
309  $it->addJoinConditions( [
310  'page' => [ 'JOIN', 'rc_cur_id = page_id' ],
311  ] );
312  $this->addIndex( $it );
313  return $it;
314  }
315 
321  protected function getDeletedCatsIterator( IDatabase $dbr ) {
322  $it = new BatchRowIterator( $dbr,
323  'recentchanges',
324  [ 'rc_timestamp' ],
325  $this->mBatchSize
326  );
327  $this->addTimestampConditions( $it, $dbr );
328  $it->addConditions( [
329  'rc_namespace' => NS_CATEGORY,
330  'rc_new' => 0,
331  'rc_log_type' => 'delete',
332  'rc_log_action' => 'delete',
333  'rc_type' => RC_LOG,
334  // We will fetch ones that do not have page record. If they do,
335  // this means they were restored, thus restoring handler will pick it up.
336  'NOT EXISTS (SELECT * FROM page WHERE page_id = rc_cur_id)',
337  ] );
338  $this->addIndex( $it );
339  $it->setFetchColumns( [ 'rc_cur_id', 'rc_title' ] );
340  return $it;
341  }
342 
348  protected function getRestoredCatsIterator( IDatabase $dbr ) {
349  $it = $this->setupChangesIterator( $dbr );
350  $it->addConditions( [
351  'rc_namespace' => NS_CATEGORY,
352  'rc_new' => 0,
353  'rc_log_type' => 'delete',
354  'rc_log_action' => 'restore',
355  'rc_type' => RC_LOG,
356  // We will only fetch ones that have page record
357  'EXISTS (SELECT page_id FROM page WHERE page_id = rc_cur_id)',
358  ] );
359  $this->addIndex( $it );
360  return $it;
361  }
362 
368  protected function getChangedCatsIterator( IDatabase $dbr, $type ) {
369  $it =
370  $this->setupChangesIterator( $dbr );
371  $it->addConditions( [
372  'rc_namespace' => NS_CATEGORY,
373  'rc_new' => 0,
374  'rc_type' => $type,
375  ] );
376  $this->addIndex( $it );
377  return $it;
378  }
379 
385  private function addTimestampConditions( BatchRowIterator $it, IDatabase $dbr ) {
386  $it->addConditions( [
387  'rc_timestamp >= ' . $dbr->addQuotes( $dbr->timestamp( $this->startTS ) ),
388  'rc_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $this->endTS ) ),
389  ] );
390  }
391 
396  private function addIndex( BatchRowIterator $it ) {
397  $it->addOptions( [
398  'USE INDEX' => [ 'recentchanges' => 'new_name_timestamp' ]
399  ] );
400  }
401 
408  protected function getCategoryLinksIterator( IDatabase $dbr, array $ids ) {
409  $it = new BatchRowIterator(
410  $dbr,
411  'categorylinks',
412  [ 'cl_from', 'cl_to' ],
413  $this->mBatchSize
414  );
415  $it->addConditions( [
416  'cl_type' => 'subcat',
417  'cl_from' => $ids
418  ] );
419  $it->setFetchColumns( [ 'cl_from', 'cl_to' ] );
420  return new RecursiveIteratorIterator( $it );
421  }
422 
427  public function getRdf() {
428  return $this->rdfWriter->drain();
429  }
430 
436  public function handleDeletes( IDatabase $dbr, $output ) {
437  // This only does "true" deletes - i.e. those that the page stays deleted
438  foreach ( $this->getDeletedCatsIterator( $dbr ) as $batch ) {
439  $deleteUrls = [];
440  foreach ( $batch as $row ) {
441  // This can produce duplicates, we don't care
442  $deleteUrls[] = '<' . $this->categoriesRdf->labelToUrl( $row->rc_title ) . '>';
443  $this->processed[$row->rc_cur_id] = true;
444  }
445  fwrite( $output, $this->getCategoriesUpdate( $dbr, $deleteUrls, [], "Deletes" ) );
446  }
447  }
448 
453  private function writeCategoryData( $row ) {
454  $this->categoriesRdf->writeCategoryData(
455  $row->rc_title,
456  $row->pp_propname === 'hiddencat',
457  (int)$row->cat_pages - (int)$row->cat_subcats - (int)$row->cat_files,
458  (int)$row->cat_subcats
459  );
460  }
461 
466  public function handleMoves( IDatabase $dbr, $output ) {
467  foreach ( $this->getMovedCatsIterator( $dbr ) as $batch ) {
468  $pages = [];
469  $deleteUrls = [];
470  foreach ( $batch as $row ) {
471  $deleteUrls[] = '<' . $this->categoriesRdf->labelToUrl( $row->rc_title ) . '>';
472 
473  if ( isset( $this->processed[$row->rc_cur_id] ) ) {
474  // We already captured this one before
475  continue;
476  }
477 
478  if ( $row->page_namespace != NS_CATEGORY ) {
479  // If page was moved out of Category:, we'll just delete
480  continue;
481  }
482  $row->rc_title = $row->page_title;
483  $this->writeCategoryData( $row );
484  $pages[$row->rc_cur_id] = $row->page_title;
485  $this->processed[$row->rc_cur_id] = true;
486  }
487 
488  fwrite( $output, $this->getCategoriesUpdate( $dbr, $deleteUrls, $pages, "Moves" ) );
489  }
490  }
491 
496  public function handleRestores( IDatabase $dbr, $output ) {
497  fwrite( $output, "# Restores\n" );
498  // This will only find those restores that were not deleted later.
499  foreach ( $this->getRestoredCatsIterator( $dbr ) as $batch ) {
500  $pages = [];
501  foreach ( $batch as $row ) {
502  if ( isset( $this->processed[$row->rc_cur_id] ) ) {
503  // We already captured this one before
504  continue;
505  }
506  $this->writeCategoryData( $row );
507  $pages[$row->rc_cur_id] = $row->rc_title;
508  $this->processed[$row->rc_cur_id] = true;
509  }
510 
511  if ( empty( $pages ) ) {
512  continue;
513  }
514 
515  $this->writeParentCategories( $dbr, $pages );
516 
517  fwrite( $output, $this->getInsertRdf() );
518  }
519  }
520 
525  public function handleAdds( IDatabase $dbr, $output ) {
526  fwrite( $output, "# Additions\n" );
527  foreach ( $this->getNewCatsIterator( $dbr ) as $batch ) {
528  $pages = [];
529  foreach ( $batch as $row ) {
530  if ( isset( $this->processed[$row->rc_cur_id] ) ) {
531  // We already captured this one before
532  continue;
533  }
534  $this->writeCategoryData( $row );
535  $pages[$row->rc_cur_id] = $row->rc_title;
536  $this->processed[$row->rc_cur_id] = true;
537  }
538 
539  if ( empty( $pages ) ) {
540  continue;
541  }
542 
543  $this->writeParentCategories( $dbr, $pages );
544  fwrite( $output, $this->getInsertRdf() );
545  }
546  }
547 
553  public function handleEdits( IDatabase $dbr, $output ) {
554  // Editing category can change hidden flag and add new parents.
555  // TODO: it's pretty expensive to update all edited categories, and most edits
556  // aren't actually interesting for us. Some way to know which are interesting?
557  // We can capture recategorization on the next step, but not change in hidden status.
558  foreach ( $this->getChangedCatsIterator( $dbr, RC_EDIT ) as $batch ) {
559  $pages = [];
560  $deleteUrls = [];
561  foreach ( $batch as $row ) {
562  // Note that on categorization event, cur_id points to
563  // the child page, not the parent category!
564  if ( isset( $this->processed[$row->rc_cur_id] ) ) {
565  // We already captured this one before
566  continue;
567  }
568  $this->writeCategoryData( $row );
569  $pages[$row->rc_cur_id] = $row->rc_title;
570  $this->processed[$row->rc_cur_id] = true;
571  $deleteUrls[] = '<' . $this->categoriesRdf->labelToUrl( $row->rc_title ) . '>';
572  }
573 
574  fwrite( $output, $this->getCategoriesUpdate( $dbr, $deleteUrls, $pages, "Edits" ) );
575  }
576  }
577 
583  public function handleCategorization( IDatabase $dbr, $output ) {
584  $processedTitle = [];
585  // Categorization change can add new parents and change counts
586  // for the parent category.
587  foreach ( $this->getChangedCatsIterator( $dbr, RC_CATEGORIZE ) as $batch ) {
588  /*
589  * Note that on categorization event, cur_id points to
590  * the child page, not the parent category!
591  * So we need to have a two-stage process, since we have ID from one
592  * category and title from another, and we need both for proper updates.
593  * TODO: For now, we do full update even though some data hasn't changed,
594  * e.g. parents for parent cat and counts for child cat.
595  */
596  foreach ( $batch as $row ) {
597  $childPages[$row->rc_cur_id] = true;
598  $parentCats[$row->rc_title] = true;
599  }
600 
601  $joinConditions = [
602  'page_props' => [
603  'LEFT JOIN',
604  [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ],
605  ],
606  'category' => [
607  'LEFT JOIN',
608  [ 'cat_title = page_title' ],
609  ],
610  ];
611 
612  $pages = [];
613  $deleteUrls = [];
614 
615  if ( !empty( $childPages ) ) {
616  // Load child rows by ID
617  $childRows = $dbr->select(
618  [ 'page', 'page_props', 'category' ],
619  [
620  'page_id',
621  'rc_title' => 'page_title',
622  'pp_propname',
623  'cat_pages',
624  'cat_subcats',
625  'cat_files',
626  ],
627  [ 'page_namespace' => NS_CATEGORY, 'page_id' => array_keys( $childPages ) ],
628  __METHOD__,
629  [],
630  $joinConditions
631  );
632  foreach ( $childRows as $row ) {
633  if ( isset( $this->processed[$row->page_id] ) ) {
634  // We already captured this one before
635  continue;
636  }
637  $this->writeCategoryData( $row );
638  $deleteUrls[] = '<' . $this->categoriesRdf->labelToUrl( $row->rc_title ) . '>';
639  $this->processed[$row->page_id] = true;
640  }
641  }
642 
643  if ( !empty( $parentCats ) ) {
644  // Load parent rows by title
645  $joinConditions = [
646  'page' => [
647  'LEFT JOIN',
648  [ 'page_title = cat_title', 'page_namespace' => NS_CATEGORY ],
649  ],
650  'page_props' => [
651  'LEFT JOIN',
652  [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ],
653  ],
654  ];
655 
656  $parentRows = $dbr->select(
657  [ 'category', 'page', 'page_props' ],
658  [
659  'page_id',
660  'rc_title' => 'cat_title',
661  'pp_propname',
662  'cat_pages',
663  'cat_subcats',
664  'cat_files',
665  ],
666  [ 'cat_title' => array_keys( $parentCats ) ],
667  __METHOD__,
668  [],
669  $joinConditions
670  );
671  foreach ( $parentRows as $row ) {
672  if ( $row->page_id && isset( $this->processed[$row->page_id] ) ) {
673  // We already captured this one before
674  continue;
675  }
676  if ( isset( $processedTitle[$row->rc_title] ) ) {
677  // We already captured this one before
678  continue;
679  }
680  $this->writeCategoryData( $row );
681  $deleteUrls[] = '<' . $this->categoriesRdf->labelToUrl( $row->rc_title ) . '>';
682  if ( $row->page_id ) {
683  $this->processed[$row->page_id] = true;
684  }
685  $processedTitle[$row->rc_title] = true;
686  }
687  }
688 
689  fwrite( $output, $this->getCategoriesUpdate( $dbr, $deleteUrls, $pages, "Changes" ) );
690  }
691  }
692 }
693 
695 require_once RUN_MAINTENANCE_IF_MAIN;
handleDeletes(IDatabase $dbr, $output)
Handle category deletes.
getDeletedCatsIterator(IDatabase $dbr)
Fetch deleted categories.
const RC_CATEGORIZE
Definition: Defines.php:142
Maintenance script to provide RDF representation of the recent changes in category tree...
getRdf()
Get accumulated RDF.
error( $err, $die=0)
Throw an error to the user.
addIndex(BatchRowIterator $it)
Need to force index, somehow on terbium the optimizer chooses wrong one.
addTimestampConditions(BatchRowIterator $it, IDatabase $dbr)
Add timestamp limits to iterator.
getMovedCatsIterator(IDatabase $dbr)
Fetch moved categories.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
getOption( $name, $default=null)
Get an option, or return the default.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Definition: maintenance.txt:39
Helper class to produce RDF representation of categories.
const SPARQL_DELETE_INSERT
Delete/Insert query.
setBatchSize( $s=0)
Set the batch size.
require_once RUN_MAINTENANCE_IF_MAIN
Definition: maintenance.txt:50
this hook is for auditing only RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition: hooks.txt:979
$wgRCMaxAge
Recentchanges items are periodically purged; entries older than this many seconds will go...
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
updateTS( $timestamp)
Generate SPARQL Update code for updating dump timestamp.
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place $output
Definition: hooks.txt:2217
writeParentCategories(IDatabase $dbr, $pages)
Write parent data for a set of categories.
getRestoredCatsIterator(IDatabase $dbr)
Fetch restored categories.
const SPARQL_INSERT
Insert query.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
getInsertRdf()
Get the text of SPARQL INSERT DATA clause.
handleRestores(IDatabase $dbr, $output)
writeCategoryData( $row)
Write category data to RDF.
DELETE
Definition: updatets.txt:1
addDescription( $text)
Set the description text.
const NS_CATEGORY
Definition: Defines.php:74
getDB( $db, $groups=[], $wiki=false)
Returns a database to be used by current maintenance script.
handleMoves(IDatabase $dbr, $output)
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
handleCategorization(IDatabase $dbr, $output)
Handles categorization changes.
getCategoriesUpdate(IDatabase $dbr, $deleteUrls, $pages, $mark)
Get SPARQL for updating set of categories.
const SPARQL_DELETE
Delete query.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
handleEdits(IDatabase $dbr, $output)
Handle edits for category texts.
handleAdds(IDatabase $dbr, $output)
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
select( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
getChangedCatsIterator(IDatabase $dbr, $type)
Fetch categorization changes or edits.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
CategoriesRdf $categoriesRdf
Categories RDF helper.
initialize()
Initialize external service classes.
const DB_REPLICA
Definition: defines.php:25
addOptions(array $options)
getCategoryLinksIterator(IDatabase $dbr, array $ids)
Get iterator for links for categories.
addQuotes( $s)
Adds quotes and backslashes.
setupChangesIterator(IDatabase $dbr, array $columns=[], array $extra_tables=[])
Set up standard iterator for retrieving category changes.
getNewCatsIterator(IDatabase $dbr)
Fetch newly created categories.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...
const RC_EDIT
Definition: Defines.php:138
const RC_LOG
Definition: Defines.php:140
addConditions(array $conditions)