first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-06-08 17:09:23 -04:00
commit df3a033196
17887 changed files with 8637778 additions and 0 deletions
@@ -0,0 +1,192 @@
<?php
/**
* @file classes/services/QueryBuilders/PKPContextQueryBuilder.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPContextQueryBuilder
*
* @ingroup query_builders
*
* @brief Base class for context (journals/presses) list query builder
*/
namespace PKP\services\queryBuilders;
use Illuminate\Support\Facades\DB;
use PKP\plugins\Hook;
use PKP\security\Role;
use PKP\services\queryBuilders\interfaces\EntityQueryBuilderInterface;
abstract class PKPContextQueryBuilder implements EntityQueryBuilderInterface
{
/** @var string The database name for this context: `journals` or `presses` */
protected $db;
/** @var string The database name for this context's settings: `journal_settings` or `press_settings` */
protected $dbSettings;
/** @var string The column name for a context ID: `journal_id` or `press_id` */
protected $dbIdColumn;
/** @var ?bool enabled or disabled contexts */
protected $isEnabled = null;
/** @var ?int Filter contexts by whether or not this user can access it when logged in */
protected $userId;
/** @var ?string search phrase */
protected $searchPhrase = null;
/** @var string[] Selected columns */
protected $columns = [];
/**
* Set isEnabled filter
*
* @param ?bool $isEnabled
*
* @return \PKP\services\queryBuilders\PKPContextQueryBuilder
*/
public function filterByIsEnabled($isEnabled)
{
$this->isEnabled = $isEnabled;
return $this;
}
/**
* Set userId filter
*
* The user id can access contexts where they are assigned to
* a user group. If the context is disabled, they must be
* assigned to ROLE_ID_MANAGER user group.
*
* @param ?int $userId
*
* @return \PKP\services\queryBuilders\PKPContextQueryBuilder
*/
public function filterByUserId($userId)
{
$this->userId = $userId;
return $this;
}
/**
* Set query search phrase
*
* @param ?string $phrase
*
* @return \PKP\services\queryBuilders\PKPContextQueryBuilder
*/
public function searchPhrase($phrase)
{
$this->searchPhrase = $phrase;
return $this;
}
/**
* @copydoc PKP\services\queryBuilders\interfaces\EntityQueryBuilderInterface::getCount()
*/
public function getCount()
{
return $this
->getQuery()
->select('c.' . $this->dbIdColumn)
->get()
->count();
}
/**
* @copydoc PKP\services\queryBuilders\interfaces\EntityQueryBuilderInterface::getIds()
*/
public function getIds()
{
return $this
->getQuery()
->select('c.' . $this->dbIdColumn)
->pluck('c.' . $this->dbIdColumn)
->toArray();
}
/**
* Get the name and basic data for a set of contexts
*
* This returns data from the main table and the name
* of the context in its primary locale.
*
* @return array
*/
public function getManySummary()
{
return $this
->getQuery()
->select([
'c.' . $this->dbIdColumn . ' as id',
'c.enabled',
'cst.setting_value as name',
'c.path as urlPath',
'c.seq',
])
->leftJoin($this->dbSettings . ' as cst', function ($q) {
$q->where('cst.' . $this->dbIdColumn, '=', DB::raw('c.' . $this->dbIdColumn))
->where('cst.setting_name', '=', 'name')
->where('cst.locale', '=', DB::raw('c.primary_locale'));
})
->orderBy('c.seq')
->get()
->toArray();
}
/**
* @copydoc PKP\services\queryBuilders\interfaces\EntityQueryBuilderInterface::getQuery()
*/
public function getQuery()
{
$this->columns[] = 'c.*';
$q = DB::table($this->db . ' as c');
if (!empty($this->isEnabled)) {
$q->where('c.enabled', '=', 1);
} elseif ($this->isEnabled === false) {
$q->where('c.enabled', '!=', 1);
}
// Filter for user id if present
$q->when(!empty($this->userId), function ($q) {
$q->whereIn('c.' . $this->dbIdColumn, function ($q) {
$q->select('context_id')
->from('user_groups')
->where(function ($q) {
$q->where('role_id', '=', Role::ROLE_ID_MANAGER)
->orWhere('c.enabled', '=', 1);
})
->whereIn('user_group_id', function ($q) {
$q->select('user_group_id')
->from('user_user_groups')
->where('user_id', '=', $this->userId);
});
});
});
// search phrase
$q->when($this->searchPhrase !== null, function ($query) {
$words = explode(' ', $this->searchPhrase);
foreach ($words as $word) {
$query->whereIn('c.' . $this->dbIdColumn, function ($query) use ($word) {
return $query->select($this->dbIdColumn)
->from($this->dbSettings)
->whereIn('setting_name', ['description', 'acronym', 'abbreviation'])
->where(DB::raw('LOWER(setting_value)'), 'LIKE', DB::raw("CONCAT('%', LOWER(?), '%')"))->addBinding($word);
});
}
});
// Add app-specific query statements
Hook::call('Context::getContexts::queryObject', [&$q, $this]);
$q->select($this->columns);
return $q;
}
}
@@ -0,0 +1,61 @@
<?php
/**
* @file classes/services/queryBuilders/PKPStatsContextQueryBuilder.php
*
* Copyright (c) 2022 Simon Fraser University
* Copyright (c) 2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPStatsContextQueryBuilder
*
* @ingroup query_builders
*
* @brief Helper class to construct a query to fetch context stats records from the
* metrics_context table.
*/
namespace PKP\services\queryBuilders;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use PKP\plugins\Hook;
use PKP\statistics\PKPStatisticsHelper;
class PKPStatsContextQueryBuilder extends PKPStatsQueryBuilder
{
/**
* Get contexts IDs
*/
public function getContextIds(): Builder
{
return $this->_getObject()
->select([PKPStatisticsHelper::STATISTICS_DIMENSION_CONTEXT_ID])
->distinct();
}
/**
* @copydoc PKPStatsQueryBuilder::_getObject()
*/
protected function _getObject(): Builder
{
$q = DB::table('metrics_context');
if (!empty($this->contextIds)) {
$q->whereIn(PKPStatisticsHelper::STATISTICS_DIMENSION_CONTEXT_ID, $this->contextIds);
}
$q->whereBetween(PKPStatisticsHelper::STATISTICS_DIMENSION_DATE, [$this->dateStart, $this->dateEnd]);
if ($this->limit > 0) {
$q->limit($this->limit);
if ($this->offset > 0) {
$q->offset($this->offset);
}
}
Hook::call('StatsContext::queryObject', [&$q, $this]);
return $q;
}
}
@@ -0,0 +1,501 @@
<?php
/**
* @file classes/services/QueryBuilders/PKPStatsEditorialQueryBuilder.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPStatsEditorialQueryBuilder
*
* @ingroup query_builders
*
* @brief Helper class to construct a query to fetch stats records from the
* metrics table.
*/
namespace PKP\services\queryBuilders;
use APP\facades\Repo;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use PKP\config\Config;
use PKP\decision\DecisionType;
use PKP\plugins\Hook;
use PKP\submission\PKPSubmission;
abstract class PKPStatsEditorialQueryBuilder
{
/** @var array Return stats for activity in these contexts */
protected $contextIds = [];
/** @var string Return stats for activity before this date */
protected $dateEnd;
/** @var string Return stats for activity after this date */
protected $dateStart;
/** @var array Return stats for activity in these sections (series in OMP) */
protected $sectionIds = [];
/** @var string The table column name for section IDs (OJS) or series IDs (OMP) */
public $sectionIdsColumn;
/**
* Set the contexts to return activity for
*
* @param array|int $contextIds
*
* @return \PKP\services\queryBuilders\PKPStatsEditorialQueryBuilder
*/
public function filterByContexts($contextIds)
{
$this->contextIds = is_array($contextIds) ? $contextIds : [$contextIds];
return $this;
}
/**
* Set the section ids to include activity for. This is stored under
* the section_id db column but in OMP refers to seriesIds.
*
* @param array|int $sectionIds
*
* @return \PKP\services\queryBuilders\PKPStatsEditorialQueryBuilder
*/
public function filterBySections($sectionIds)
{
$this->sectionIds = is_array($sectionIds) ? $sectionIds : [$sectionIds];
return $this;
}
/**
* Set the date to get activity before
*
* @param string $dateEnd YYYY-MM-DD
*
* @return \PKP\services\queryBuilders\PKPStatsEditorialQueryBuilder
*/
public function before($dateEnd)
{
$this->dateEnd = $dateEnd;
return $this;
}
/**
* Set the date to get activity after
*
* @param string $dateStart YYYY-MM-DD
*
* @return \PKP\services\queryBuilders\PKPStatsEditorialQueryBuilder
*/
public function after($dateStart)
{
$this->dateStart = $dateStart;
return $this;
}
/**
* Get the count of submissions received
*
* @return int
*/
public function countSubmissionsReceived()
{
$q = $this->_getObject();
if ($this->dateStart) {
$q->where('s.date_submitted', '>=', $this->dateStart);
}
if ($this->dateEnd) {
$q->where('s.date_submitted', '<=', $this->dateEnd);
}
return $q->count();
}
/**
* Get the count of submissions that have received one or more
* editor decisions
*
* @param array $decisions One or more Decision::*
* @param bool $forSubmittedDate How date restrictions should be applied.
* A false value will count the number of submissions with an editorial
* decision within the date range. A true value will count the number of
* submissions received within the date range which eventually received
* an editorial decision.
*
* @return int
*/
public function countByDecisions($decisions, $forSubmittedDate = false)
{
$q = $this->_getObject();
$q->leftJoin('edit_decisions as ed', 's.submission_id', '=', 'ed.submission_id')
->whereIn('ed.decision', $decisions);
if ($forSubmittedDate) {
if ($this->dateStart) {
$q->where('s.date_submitted', '>=', $this->dateStart);
}
if ($this->dateEnd) {
// Include date time values up to the end of the day
$dateTime = new \DateTime($this->dateEnd);
$dateTime->add(new \DateInterval('P1D'));
$q->where('s.date_submitted', '<', $dateTime->format('Y-m-d'));
}
} else {
if ($this->dateStart) {
$q->where('ed.date_decided', '>=', $this->dateStart);
}
if ($this->dateEnd) {
// Include date time values up to the end of the day
$dateTime = new \DateTime($this->dateEnd);
$dateTime->add(new \DateInterval('P1D'));
$q->where('ed.date_decided', '<', $dateTime->format('Y-m-d'));
}
}
// Ensure that the decisions being counted have not been
// reversed. For example, a submission may have been accepted
// and then later declined. We check the current status to
// exclude submissions where the status doesn't match the
// decisions we are looking for.
$declineDecisions = array_map(function (DecisionType $decisionType) {
return $decisionType->getDecision();
}, Repo::decision()->getDeclineDecisionTypes());
if (count(array_intersect($declineDecisions, $decisions))) {
$q->where('s.status', '=', PKPSubmission::STATUS_DECLINED);
} else {
$q->where('s.status', '!=', PKPSubmission::STATUS_DECLINED);
}
$q->select(DB::raw('COUNT(DISTINCT s.submission_id) as count'));
return $q->get()->first()->count;
}
/**
* Get the count of submissions by one or more status
*
* @param int|array $status One or more of PKPSubmission::STATUS_*
*
* @return int
*/
public function countByStatus($status)
{
return $this->_getObject()
->whereIn('s.status', (array) $status)
->count();
}
/**
* Get the count of active submissions by one or more stages
*
* @param array $stages One or more of WORKFLOW_STAGE_ID_*
*
* @return int
*/
public function countActiveByStages($stages)
{
return $this->_getObject()
->where('s.status', '=', PKPSubmission::STATUS_QUEUED)
->whereIn('s.stage_id', $stages)
->count();
}
/**
* Get the count of published submissions
*
* @return int
*/
public function countPublished()
{
$q = $this->_getObject()
->where('s.status', '=', PKPSubmission::STATUS_PUBLISHED);
// Only match against the publication date of a
// submission's first published publication so
// that updated versions are excluded.
if ($this->dateStart || $this->dateEnd) {
$q->leftJoin('publications as p', function ($q) {
$q->where('p.publication_id', function ($q) {
$q->from('publications as p2')
->where('p2.submission_id', '=', DB::raw('s.submission_id'))
->where('p2.status', '=', PKPSubmission::STATUS_PUBLISHED)
->orderBy('p2.date_published', 'ASC')
->limit(1)
->select('p2.publication_id');
});
});
if ($this->dateStart) {
$q->where('p.date_published', '>=', $this->dateStart);
}
if ($this->dateEnd) {
$q->where('p.date_published', '<=', $this->dateEnd);
}
}
return $q->count();
}
/**
* Get the number of days to reach a particular editor decision
*
* This list includes any completed submission which has received
* one of the editor decisions.
*
* @param array $decisions One or more Decision::*
*
* @return array Days between submission and the first decision in
* the list of requested submissions
*/
public function getDaysToDecisions($decisions)
{
$q = $this->_getDaysToDecisionsObject($decisions);
$dateDiff = $this->_dateDiff('ed.date_decided', 's.date_submitted');
$q->select(DB::raw($dateDiff . ' as time'));
return $q->pluck('time')->toArray();
}
/**
* Get the average number of days to reach a particular
* editor decision
*
* This average includes any completed submission which has received
* one of the editor decisions.
*
* @param array $decisions One or more Decision::*
*
* @return float Average days between submission and the first decision
* in the list of requested submissions
*/
public function getAverageDaysToDecisions($decisions)
{
$q = $this->_getDaysToDecisionsObject($decisions);
$dateDiff = $this->_dateDiff('ed.date_decided', 's.date_submitted');
$q->select(DB::raw('AVG(' . $dateDiff . ') as average'));
return $q->get()->first()->average;
}
/**
* Get the first and last date of submissions received
*
* @return array [min, max]
*/
public function getSubmissionsReceivedDates()
{
$q = $this->_getObject();
return [$q->min('s.date_submitted'), $q->max('s.date_submitted')];
}
/**
* Get the first and last date of submissions published
*
* @return array [min, max]
*/
public function getPublishedDates()
{
$q = $this->_getObject()
->where('s.status', '=', PKPSubmission::STATUS_PUBLISHED)
// Only match against the publication date of a
// submission's first published publication so
// that updated versions are excluded.
->leftJoin('publications as p', function ($q) {
$q->where('p.publication_id', function ($q) {
$q->from('publications as p2')
->where('p2.submission_id', '=', DB::raw('s.submission_id'))
->where('p2.status', '=', PKPSubmission::STATUS_PUBLISHED)
->orderBy('p2.date_published', 'ASC')
->limit(1)
->select('p2.publication_id');
});
});
return [$q->min('p.date_published'), $q->max('p.date_published')];
}
/**
* Get the first and last date that an editorial decision was made
*
* @param array $decisions One or more Decision::*
*
* @return array [min, max]
*/
public function getDecisionsDates($decisions)
{
$q = $this->_getObject();
$q->leftJoin('edit_decisions as ed', 's.submission_id', '=', 'ed.submission_id')
->whereIn('ed.decision', $decisions);
// Ensure that the decisions being counted have not been
// reversed. For example, a submission may have been accepted
// and then later declined. We check the current status to
// exclude submissions where the status doesn't match the
// decisions we are looking for.
$declineDecisions = array_map(function (DecisionType $decisionType) {
return $decisionType->getDecision();
}, Repo::decision()->getDeclineDecisionTypes());
if (count(array_intersect($declineDecisions, $decisions))) {
$q->where('s.status', '=', PKPSubmission::STATUS_DECLINED);
} else {
$q->where('s.status', '!=', PKPSubmission::STATUS_DECLINED);
}
return [$q->min('ed.date_decided'), $q->max('ed.date_decided')];
}
/**
* Generate a base query object with context and section filters.
*/
protected function _getBaseQuery(): Builder
{
$q = DB::table('submissions as s');
if (!empty($this->contextIds)) {
$q->whereIn('s.context_id', $this->contextIds);
}
if (!empty($this->sectionIds)) {
$q->leftJoin('publications as ps', 's.current_publication_id', '=', 'ps.publication_id')
->whereIn("ps.{$this->sectionIdsColumn}", $this->sectionIds)
->whereNotNull('ps.publication_id');
}
// First publication included to flag imported submissions through heuristics
$q->leftJoin(
'publications as pi',
fn (Builder $q) => $q->where(
'pi.publication_id',
fn (Builder $q) => $q->from('publications as pi2')
->whereColumn('pi2.submission_id', '=', 's.submission_id')
->where('pi2.status', '=', PKPSubmission::STATUS_PUBLISHED)
->orderBy('pi2.date_published', 'ASC')
->limit(1)
->select('pi2.publication_id')
)
);
return $q;
}
/**
* Generate a query object based on the configured conditions.
* Incomplete and imported submissions are excluded.
*
* The dateStart and dateEnd filters are not handled here because
* the dates must be applied differently for each set of data.
*/
protected function _getObject(): Builder
{
$q = $this->_getBaseQuery();
// Exclude incomplete submissions
$q->where('s.submission_progress', '=', 0);
// Exclude submissions when the date_submitted is later
// than the first date_published. This prevents imported
// submissions from being counted in editorial stats.
$q->where(
fn (Builder $q) => $q->whereNull('pi.date_published')
->orWhere(DB::raw('CAST(s.date_submitted AS DATE)'), '<=', DB::raw('pi.date_published'))
);
Hook::call('Stats::editorial::queryObject', [&$q, $this]);
return $q;
}
/**
* Generate a query object to get a submission's first
* decision of the requested decision types
*
* Pass an empty $decisions array to return the number of days to
* _any_ decision.
*
* @param array $decisions One or more Decision::*
*
* @return Builder
*/
protected function _getDaysToDecisionsObject($decisions)
{
$q = $this->_getObject();
$q->leftJoin('edit_decisions as ed', function ($q) use ($decisions) {
$q->where('ed.edit_decision_id', function ($q) use ($decisions) {
$q->from('edit_decisions as ed2')
->where('ed2.submission_id', '=', DB::raw('s.submission_id'));
if (!empty($decisions)) {
$q->whereIn('ed2.decision', $decisions);
}
$q->orderBy('ed2.date_decided', 'ASC')
->limit(1)
->select('ed2.edit_decision_id');
});
});
$q->whereNotNull('ed.submission_id')
->whereNotNull('s.date_submitted');
if ($this->dateStart) {
$q->where('s.date_submitted', '>=', $this->dateStart);
}
if ($this->dateEnd) {
// Include date time values up to the end of the day
$dateTime = new \DateTime($this->dateEnd);
$dateTime->add(new \DateInterval('P1D'));
$q->where('s.date_submitted', '<=', $dateTime->format('Y-m-d'));
}
return $q;
}
/**
* Get the count of imported submissions.
* Not counted by static::countSubmissionsReceived().
*
* @return int
*/
public function countImported()
{
return $this->_getBaseQuery()
->where(DB::raw('CAST(s.date_submitted AS DATE)'), '>', DB::raw('pi.date_published'))
->when($this->dateStart, fn (Builder $q) => $q->where('s.date_submitted', '>=', $this->dateStart))
->when($this->dateEnd, fn (Builder $q) => $q->where('s.date_submitted', '<=', $this->dateEnd))
->count();
}
/**
* Get the count of incomplete submissions.
* Not counted by static::countSubmissionsReceived().
*
* @return int
*/
public function countInProgress()
{
return $this->_getBaseQuery()
->where('s.submission_progress', '<>', 0)
->when($this->dateStart, fn (Builder $q) => $q->where('s.date_submitted', '>=', $this->dateStart))
->when($this->dateEnd, fn (Builder $q) => $q->where('s.date_submitted', '<=', $this->dateEnd))
->count();
}
/**
* Get the count of submissions skipped by the other statistics
*/
public function countSkipped(): int
{
return $this->countInProgress() + $this->countImported();
}
/**
* Retrieves a suitable diff by days clause according to the active database driver
*
* @return string
*/
private function _dateDiff(string $leftDate, string $rightDate)
{
switch (Config::getVar('database', 'driver')) {
case 'mysql':
case 'mysqli':
return 'DATEDIFF(' . $leftDate . ',' . $rightDate . ')';
}
return "DATE_PART('day', " . $leftDate . ' - ' . $rightDate . ')';
}
}
@@ -0,0 +1,266 @@
<?php
/**
* @file classes/services/queryBuilders/PKPStatsGeoQueryBuilder.php
*
* Copyright (c) 2022 Simon Fraser University
* Copyright (c) 2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPStatsGeoQueryBuilder
*
* @ingroup query_builders
*
* @brief Helper class to construct a query to fetch geographic stats records from the
* metrics_submission_geo_monthly table.
*/
namespace PKP\services\queryBuilders;
use APP\statistics\StatisticsHelper;
use APP\submission\Submission;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use PKP\config\Config;
use PKP\plugins\Hook;
abstract class PKPStatsGeoQueryBuilder extends PKPStatsQueryBuilder
{
/** Include records for these sections/series */
protected array $pkpSectionIds = [];
/** Include records for these submissions */
protected array $submissionIds = [];
/** Include records for these countries */
protected array $countries = [];
/** Include records for these regions */
protected array $regions = [];
/** Include records for these cities */
protected array $cities = [];
/** Application specific name of the section column */
abstract public function getSectionColumn(): string;
/**
* Set the sections/series to get records for
*/
public function filterByPKPSections(array $pkpSectionIds): self
{
$this->pkpSectionIds = $pkpSectionIds;
return $this;
}
/**
* Set the submission to get records for
*/
public function filterBySubmissions(array $submissionIds): self
{
$this->submissionIds = $submissionIds;
return $this;
}
/**
* Set the countries to get records for
*/
public function filterByCountries(array $countries): self
{
$this->countries = $countries;
return $this;
}
/**
* Set the regions to get records for
*/
public function filterByRegions(array $regions): self
{
$this->regions = $regions;
return $this;
}
/**
* Set the cities to get records for
*/
public function filterByCities(array $cities): self
{
$this->cities = $cities;
return $this;
}
/**
* Get Geo data
*/
public function getGeoData(array $groupBy): Builder
{
return $this->_getObject()
->select($groupBy)
->groupBy($groupBy);
}
/**
* @copydoc PKPStatsQueryBuilder::getSum()
*/
public function getSum(array $groupBy = []): Builder
{
$q = $this->_getObject();
// Build the select and group by clauses.
if (!empty($groupBy)) {
$q->select($groupBy);
$q->groupBy($groupBy);
}
$q->addSelect(DB::raw('SUM(metric) AS metric'));
$q->addSelect(DB::raw('SUM(metric_unique) AS metric_unique'));
return $q;
}
/**
* Consider/add application specific queries
*/
protected function _getAppSpecificQuery(Builder &$q): void
{
}
/**
* @copydoc PKPStatsQueryBuilder::_getObject()
*/
protected function _getObject(): Builder
{
// consider only monthly DB table
$q = DB::table('metrics_submission_geo_monthly');
if (!empty($this->contextIds)) {
$q->whereIn(StatisticsHelper::STATISTICS_DIMENSION_CONTEXT_ID, $this->contextIds);
}
if (!empty($this->submissionIds)) {
$q->whereIn(StatisticsHelper::STATISTICS_DIMENSION_SUBMISSION_ID, $this->submissionIds);
}
if (!empty($this->countries)) {
$q->whereIn(StatisticsHelper::STATISTICS_DIMENSION_COUNTRY, $this->countries);
}
if (!empty($this->regions)) {
// get first region (so that we can use where and then orWhere query)
$fistCountryRegionCode = array_shift($this->regions);
// regions must be in a form countryCode-regionCode
[$country, $region] = explode('-', $fistCountryRegionCode);
$q->where(function ($q) use ($country, $region) {
$q->where(StatisticsHelper::STATISTICS_DIMENSION_COUNTRY, $country)
->where(StatisticsHelper::STATISTICS_DIMENSION_REGION, $region);
});
foreach ($this->regions as $countryRegioncode) {
// regions must be in a form countryCode-regionCode
[$country, $region] = explode('-', $countryRegioncode);
$q->orWhere(function ($q) use ($country, $region) {
$q->where(StatisticsHelper::STATISTICS_DIMENSION_COUNTRY, $country)
->where(StatisticsHelper::STATISTICS_DIMENSION_REGION, $region);
});
}
}
if (!empty($this->cities)) {
// get first city (so that we can use where and then orWhere query)
$fistCountryRegionCity = array_shift($this->cities);
// cities must be in a form countryCode-regionCode-cityName
[$country, $region, $city] = explode('-', $fistCountryRegionCity);
$q->where(function ($q) use ($country, $region, $city) {
$q->where(StatisticsHelper::STATISTICS_DIMENSION_COUNTRY, $country)
->where(StatisticsHelper::STATISTICS_DIMENSION_REGION, $region)
->where(StatisticsHelper::STATISTICS_DIMENSION_CITY, 'like', $city . '%');
});
foreach ($this->cities as $countryRegionCity) {
// cities must be in a form countryCode-regionCode-cityName
[$country, $region, $city] = explode('-', $countryRegionCity);
$q->orWhere(function ($q) use ($country, $region, $city) {
$q->where(StatisticsHelper::STATISTICS_DIMENSION_COUNTRY, $country)
->where(StatisticsHelper::STATISTICS_DIMENSION_REGION, $region)
->where(StatisticsHelper::STATISTICS_DIMENSION_CITY, 'like', $city . '%');
});
}
}
$q->whereBetween(StatisticsHelper::STATISTICS_DIMENSION_MONTH, [date_format(date_create($this->dateStart), 'Ym'), date_format(date_create($this->dateEnd), 'Ym')]);
if (!empty($this->pkpSectionIds)) {
$sectionColumn = 'p.' . $this->getSectionColumn();
$sectionSubmissionIds = DB::table('publications as p')->select('p.submission_id')->distinct()
->from('publications as p')
->where('p.status', Submission::STATUS_PUBLISHED)
->whereIn($sectionColumn, $this->pkpSectionIds);
$q->joinSub($sectionSubmissionIds, 'ss', function ($join) {
$join->on('metrics_submission_geo_monthly.' . StatisticsHelper::STATISTICS_DIMENSION_SUBMISSION_ID, '=', 'ss.submission_id');
});
}
$this->_getAppSpecificQuery($q);
if ($this->limit > 0) {
$q->limit($this->limit);
if ($this->offset > 0) {
$q->offset($this->offset);
}
}
Hook::call('StatsGeo::queryObject', [&$q, $this]);
return $q;
}
/**
* Do usage stats data already exist for the given month
*
* @param string $month Month in the form YYYYMM
*/
public function monthExists(string $month): bool
{
return DB::table('metrics_submission_geo_monthly')
->where(StatisticsHelper::STATISTICS_DIMENSION_MONTH, $month)->exists();
}
/**
* Delete daily usage metrics for a month
*
* @param string $month Month in the form YYYYMM
*/
public function deleteDailyMetrics(string $month): void
{
// Construct the SQL part depending on the DB
$monthFormatSql = "DATE_FORMAT(date, '%Y%m')";
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
$monthFormatSql = "to_char(date, 'YYYYMM')";
}
DB::table('metrics_submission_geo_daily')->where(DB::raw($monthFormatSql), '=', $month)->delete();
}
/**
* Delete monthly usage metrics for a month
*
* @param string $month Month in the form YYYYMM
*/
public function deleteMonthlyMetrics(string $month): void
{
DB::table('metrics_submission_geo_monthly')->where('month', $month)->delete();
}
/**
* Aggregate daily usage metrics by a month
*
* @param string $month Month in the form YYYYMM
*/
public function addMonthlyMetrics(string $month): void
{
// Construct the SQL part depending on the DB
$monthFormatSql = "CAST(DATE_FORMAT(gd.date, '%Y%m') AS UNSIGNED)";
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
$monthFormatSql = "to_char(gd.date, 'YYYYMM')::integer";
}
$selectSubmissionGeoDaily = DB::table('metrics_submission_geo_daily as gd')
->select(DB::raw("gd.context_id, gd.submission_id, COALESCE(gd.country, ''), COALESCE(gd.region, ''), COALESCE(gd.city, ''), {$monthFormatSql} as gdmonth, SUM(gd.metric), SUM(gd.metric_unique)"))
->whereRaw("{$monthFormatSql} = ?", [$month])
->groupBy(DB::raw('gd.context_id, gd.submission_id, gd.country, gd.region, gd.city, gdmonth'));
DB::table('metrics_submission_geo_monthly')->insertUsing(['context_id', 'submission_id', 'country', 'region', 'city', 'month', 'metric', 'metric_unique'], $selectSubmissionGeoDaily);
}
}
@@ -0,0 +1,192 @@
<?php
/**
* @file classes/services/queryBuilders/PKPStatsPublicationQueryBuilder.php
*
* Copyright (c) 2022 Simon Fraser University
* Copyright (c) 2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPStatsPublicationQueryBuilder
*
* @ingroup query_builders
*
* @brief Helper class to construct a query to fetch stats records from the
* metrics_submission table.
*/
namespace PKP\services\queryBuilders;
use APP\core\Application;
use APP\submission\Submission;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use PKP\plugins\Hook;
use PKP\statistics\PKPStatisticsHelper;
abstract class PKPStatsPublicationQueryBuilder extends PKPStatsQueryBuilder
{
/**
*Include records for one of these object types:
* Application::ASSOC_TYPE_SUBMISSION, Application::ASSOC_TYPE_SUBMISSION_FILE, Application::ASSOC_TYPE_SUBMISSION_FILE_COUNTER_OTHER
*/
protected array $assocTypes = [];
/** Include records for these file types: PKPStatisticsHelper::STATISTICS_FILE_TYPE_* */
protected array $fileTypes = [];
/** Include records for these sections/series */
protected array $pkpSectionIds = [];
/** Include records for these submissions */
protected array $submissionIds = [];
/** Include records for these representations (galley or publication format) */
protected array $representationIds = [];
/** Include records for these submission files */
protected array $submissionFileIds = [];
/** Application specific name of the section column */
abstract public function getSectionColumn(): string;
/**
* Set the sections/series to get records for
*/
public function filterByPKPSections(array $pkpSectionIds): self
{
$this->pkpSectionIds = $pkpSectionIds;
return $this;
}
/**
* Set the submissions to get records for
*/
public function filterBySubmissions(array $submissionIds): self
{
$this->submissionIds = $submissionIds;
return $this;
}
/**
* Set the representations to get records for
*/
public function filterByRepresentations(array $representationIds): self
{
$this->representationIds = $representationIds;
return $this;
}
/**
* Set the files to get records for
*/
public function filterBySubmissionFiles(array $submissionFileIds): self
{
$this->submissionFileIds = $submissionFileIds;
return $this;
}
/**
* Set the assocTypes to get records for
*/
public function filterByAssocTypes(array $assocTypes): self
{
$this->assocTypes = $assocTypes;
return $this;
}
/**
* Set the galley file type to get records for
*/
public function filterByFileTypes(array $fileTypes): self
{
$this->fileTypes = $fileTypes;
return $this;
}
/**
* Get submission IDs
*/
public function getSubmissionIds(): Builder
{
return $this->_getObject()
->select(['metrics_submission.' . PKPStatisticsHelper::STATISTICS_DIMENSION_SUBMISSION_ID])
->distinct();
}
/**
* @copydoc PKPStatsQueryBuilder::getSum()
*/
public function getSum(array $groupBy = []): Builder
{
$groupBy = array_map(function ($column) {
return $column == PKPStatisticsHelper::STATISTICS_DIMENSION_SUBMISSION_ID ? 'metrics_submission.' . $column : $column;
}, $groupBy);
return parent::getSum($groupBy);
}
/**
* Consider/add application specific queries
*/
protected function _getAppSpecificQuery(Builder &$q): void
{
}
/**
* @copydoc PKPStatsQueryBuilder::_getObject()
*/
protected function _getObject(): Builder
{
$q = DB::table('metrics_submission');
if (!empty($this->contextIds)) {
$q->whereIn(PKPStatisticsHelper::STATISTICS_DIMENSION_CONTEXT_ID, $this->contextIds);
}
if (!empty($this->submissionIds)) {
$q->whereIn('metrics_submission.' . PKPStatisticsHelper::STATISTICS_DIMENSION_SUBMISSION_ID, $this->submissionIds);
}
if (!empty($this->assocTypes)) {
$q->whereIn(PKPStatisticsHelper::STATISTICS_DIMENSION_ASSOC_TYPE, $this->assocTypes);
}
if (!empty($this->fileTypes)) {
$q->whereIn(PKPStatisticsHelper::STATISTICS_DIMENSION_FILE_TYPE, $this->fileTypes);
}
if (!empty($this->representationIds)) {
$q->whereIn(PKPStatisticsHelper::STATISTICS_DIMENSION_REPRESENTATION_ID, $this->representationIds);
}
if (!empty($this->submissionFileIds)) {
$q->whereIn(PKPStatisticsHelper::STATISTICS_DIMENSION_SUBMISSION_FILE_ID, $this->submissionFileIds);
}
$q->whereBetween(PKPStatisticsHelper::STATISTICS_DIMENSION_DATE, [$this->dateStart, $this->dateEnd]);
if (!empty($this->pkpSectionIds)) {
$sectionColumn = 'p.' . $this->getSectionColumn();
$sectionSubmissionIds = DB::table('publications as p')->select('p.submission_id')->distinct()
->from('publications as p')
->where('p.status', Submission::STATUS_PUBLISHED)
->whereIn($sectionColumn, $this->pkpSectionIds);
$q->joinSub($sectionSubmissionIds, 'ss', function ($join) {
$join->on('metrics_submission.' . PKPStatisticsHelper::STATISTICS_DIMENSION_SUBMISSION_ID, '=', 'ss.submission_id');
});
}
$this->_getAppSpecificQuery($q);
if ($this->limit > 0) {
$q->limit($this->limit);
if ($this->offset > 0) {
$q->offset($this->offset);
}
}
Hook::call('StatsPublication::queryObject', [&$q, $this]);
return $q;
}
}
@@ -0,0 +1,164 @@
<?php
/**
* @file classes/services/queryBuilders/PKPStatsQueryBuilder.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPStatsQueryBuilder
*
* @ingroup query_builders
*
* @brief Base class for statistics query builders.
*/
namespace PKP\services\queryBuilders;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use PKP\config\Config;
use PKP\statistics\PKPStatisticsHelper;
abstract class PKPStatsQueryBuilder
{
/** Include records for these contexts */
protected array $contextIds = [];
/** Include records from this date or before. Default: yesterday's date */
protected string $dateEnd;
/** Include records from this date or after. Default: PKPStatisticsHelper::STATISTICS_EARLIEST_DATE */
protected string $dateStart;
/** The count of records to return */
protected int $limit = 0;
/** The offset of records to return */
protected int $offset = 0;
/**
* Set the contexts to get records for
*/
public function filterByContexts(array $contextIds): self
{
$this->contextIds = $contextIds;
return $this;
}
/**
* Set the date before which to get records
*
* @param string $dateEnd YYYY-MM-DD
*
*/
public function before(string $dateEnd): self
{
$this->dateEnd = $dateEnd;
return $this;
}
/**
* Set the date after which to get records
*
* @param string $dateStart YYYY-MM-DD
*
*/
public function after(string $dateStart): self
{
$this->dateStart = $dateStart;
return $this;
}
/**
* Set the count of records to return
*/
public function limit(int $limit): self
{
$this->limit = $limit;
return $this;
}
/**
* Set the offset of records to return
*/
public function offset(int $offset): self
{
$this->offset = $offset;
return $this;
}
/**
* Get the sum of all matching records
*
* Use this method to get the total X views. Pass a
* $groupBy argument to get the total X views for each
* object, grouped by one or more columns.
*
* @param array $groupBy One or more columns to group by
*
*/
public function getSum(array $groupBy = []): Builder
{
$selectColumns = $groupBy;
$selectColumns = $this->getSelectColumns($selectColumns);
$q = $this->_getObject();
// Build the select and group by clauses.
if (!empty($selectColumns)) {
$q->select($selectColumns);
if (!empty($groupBy)) {
$q->groupBy($groupBy);
}
}
$q->addSelect(DB::raw('SUM(metric) AS metric'));
return $q;
}
/**
* Generate a query object based on the configured conditions.
*
* Public methods should call this method to set up the query
* object and apply any additional selection, grouping and
* ordering conditions.
*/
abstract protected function _getObject(): Builder;
/**
* Get appropriate SQL code for columns in the select part of the query
*/
protected function getSelectColumns(array $selectColumns): array
{
if (!in_array(PKPStatisticsHelper::STATISTICS_DIMENSION_YEAR, $selectColumns)
&& !in_array(PKPStatisticsHelper::STATISTICS_DIMENSION_MONTH, $selectColumns)
&& !in_array(PKPStatisticsHelper::STATISTICS_DIMENSION_DAY, $selectColumns)) {
return $selectColumns;
}
foreach ($selectColumns as $i => $selectColumn) {
if ($selectColumn == PKPStatisticsHelper::STATISTICS_DIMENSION_YEAR) {
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
// date_trunc: Values of type date are cast automatically to timestamp. So cast them back to date.
$selectColumns[$i] = DB::raw("date_trunc('year', date)::timestamp::date AS year");
} else {
$selectColumns[$i] = DB::raw("date_format(date, '%Y-01-01') AS year");
}
break;
} elseif ($selectColumn == PKPStatisticsHelper::STATISTICS_DIMENSION_MONTH) {
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
// date_trunc: Values of type date are cast automatically to timestamp. So cast them back to date.
$selectColumns[$i] = DB::raw("date_trunc('month', date)::timestamp::date AS month");
} else {
$selectColumns[$i] = DB::raw("date_format(date, '%Y-%m-01') AS month");
}
break;
} elseif ($selectColumn == PKPStatisticsHelper::STATISTICS_DIMENSION_DAY) {
$selectColumns[$i] = DB::raw('date AS day');
break;
}
}
return $selectColumns;
}
}
@@ -0,0 +1,236 @@
<?php
/**
* @file classes/services/queryBuilders/PKPStatsSushiQueryBuilder.php
*
* Copyright (c) 2022 Simon Fraser University
* Copyright (c) 2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPStatsSushiQueryBuilder
*
* @ingroup query_builders
*
* @brief Helper class to construct a query to fetch COUNTER stats records from the
* metrics_counter_submission_monthly or metrics_counter_submission_institution_monthly table.
*/
namespace PKP\services\queryBuilders;
use APP\statistics\StatisticsHelper;
use APP\submission\Submission;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use PKP\config\Config;
use PKP\plugins\Hook;
class PKPStatsSushiQueryBuilder extends PKPStatsQueryBuilder
{
/** Include records for the submissions that have these years of publications (YOP) */
protected array $yearsOfPublication = [];
/**Include records for these submissions */
protected array $submissionIds = [];
/**Include records for this institution */
protected int $institutionId = 0;
/**
* Set the year of publication (YOP) of submissions to get records for
*/
public function filterByYOP(array $yearsOfPublication): self
{
$this->yearsOfPublication = $yearsOfPublication;
return $this;
}
/**
* Set the submissions to get records for
*/
public function filterBySubmissions(array $submissionIds): self
{
$this->submissionIds = $submissionIds;
return $this;
}
/**
* Set the institution to get records for
*/
public function filterByInstitution(int $institutionId): self
{
$this->institutionId = $institutionId;
return $this;
}
/**
* @copydoc PKPStatsQueryBuilder::getSum()
*/
public function getSum(array $groupBy = []): Builder
{
$selectColumns = $groupBy;
$q = $this->_getObject();
// consider YOP
if (in_array('YOP', $selectColumns)) {
// left join the table publications, if the filter is not set i.e. the left join is not considered yet in _getObject()
if (empty($this->yearsOfPublication)) {
$q->leftJoin('publications as p', function ($q) {
$q->on('p.submission_id', '=', 'm.submission_id')
->whereIn('p.publication_id', function ($q) {
$q->selectRaw('MIN(p2.publication_id)')
->from('publications as p2')
->where('p2.status', Submission::STATUS_PUBLISHED)
->where('p2.submission_id', '=', DB::raw('m.submission_id'));
});
});
}
foreach ($selectColumns as $i => $selectColumn) {
if ($selectColumn == 'YOP') {
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
$selectColumns[$i] = DB::raw('EXTRACT(YEAR FROM p.date_published) as "YOP"');
} else {
$selectColumns[$i] = DB::raw('YEAR(STR_TO_DATE(p.date_published, "%Y-%m-%d")) as YOP');
}
break;
}
}
}
// Build the select and group by clauses.
if (!empty($selectColumns)) {
$q->select($selectColumns);
if (!empty($groupBy)) {
$q->groupBy($groupBy);
}
}
$counterMetricsColumns = StatisticsHelper::getCounterMetricsColumns();
foreach ($counterMetricsColumns as $counterMetricsColumn) {
$q->addSelect(DB::raw("SUM({$counterMetricsColumn}) AS {$counterMetricsColumn}"));
}
return $q;
}
/**
* @copydoc PKPStatsQueryBuilder::_getObject()
*/
protected function _getObject(): Builder
{
if ($this->institutionId === 0) {
$q = DB::table('metrics_counter_submission_monthly as m');
} else {
$q = DB::table('metrics_counter_submission_institution_monthly as m');
}
if (!empty($this->yearsOfPublication)) {
$q->leftJoin('publications as p', function ($q) {
$q->on('p.submission_id', '=', 'm.submission_id')
->whereIn('p.publication_id', function ($q) {
$q->selectRaw('MIN(p2.publication_id)')
->from('publications as p2')
->where('p2.status', Submission::STATUS_PUBLISHED)
->where('p2.submission_id', '=', DB::raw('m.submission_id'));
});
});
foreach ($this->yearsOfPublication as $yop) {
if (preg_match('/\d{4}/', $yop)) {
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
$q->where(DB::raw('EXTRACT(YEAR FROM p.date_published)'), '=', $yop);
} else {
$q->where(DB::raw('YEAR(STR_TO_DATE(p.date_published, "%Y-%m-%d"))'), '=', $yop);
}
} elseif (preg_match('/\d{4}-\d{4}/', $yop)) {
$years = explode('-', $yop);
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
$q->whereBetween(DB::raw('EXTRACT(YEAR FROM p.date_published)'), $years);
} else {
$q->whereBetween(DB::raw('YEAR(STR_TO_DATE(p.date_published, "%Y-%m-%d"))'), $years);
}
}
}
}
if (!empty($this->contextIds)) {
$q->whereIn('m.' . StatisticsHelper::STATISTICS_DIMENSION_CONTEXT_ID, $this->contextIds);
}
if (!empty($this->submissionIds)) {
$q->whereIn('m.' . StatisticsHelper::STATISTICS_DIMENSION_SUBMISSION_ID, $this->submissionIds);
}
$q->whereBetween('m.' . StatisticsHelper::STATISTICS_DIMENSION_MONTH, [date_format(date_create($this->dateStart), 'Ym'), date_format(date_create($this->dateEnd), 'Ym')]);
Hook::call('StatsSushi::queryObject', [&$q, $this]);
return $q;
}
/**
* Do usage stats data already exist for the given month
* Consider only the table metrics_counter_submission_monthly, because
* it always contains data, while metrics_counter_submission_institution_monthly
* could not contain data.
*
* @param string $month Month in the form YYYYMM
*/
public function monthExists(string $month): bool
{
return DB::table('metrics_counter_submission_monthly as m')
->where(StatisticsHelper::STATISTICS_DIMENSION_MONTH, $month)->exists();
}
/**
* Delete daily usage stats for a month
*
* @param string $month Month in the form YYYYMM
*/
public function deleteDailyMetrics(string $month): void
{
// Construct the SQL part depending on the DB
$monthFormatSql = "DATE_FORMAT(date, '%Y%m')";
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
$monthFormatSql = "to_char(date, 'YYYYMM')";
}
DB::table('metrics_counter_submission_daily')->where(DB::raw($monthFormatSql), '=', $month)->delete();
DB::table('metrics_counter_submission_institution_daily')->where(DB::raw($monthFormatSql), '=', $month)->delete();
}
/**
* Delete monthly usage metrics for a month
*
* @param string $month Month in the form YYYYMM
*/
public function deleteMonthlyMetrics(string $month): void
{
DB::table('metrics_counter_submission_monthly')->where('month', $month)->delete();
DB::table('metrics_counter_submission_institution_monthly')->where('month', $month)->delete();
}
/**
* Aggregate daily usage metrics by a month
*
* @param string $month Month in the form YYYYMM
*/
public function addMonthlyMetrics(string $month): void
{
// Construct the SQL part depending on the DB
$monthFormatSql = "CAST(DATE_FORMAT(csd.date, '%Y%m') AS UNSIGNED)";
if (substr(Config::getVar('database', 'driver'), 0, strlen('postgres')) === 'postgres') {
$monthFormatSql = "to_char(csd.date, 'YYYYMM')::integer";
}
// Get the application specific metrics columns
$counterMetricsColumns = StatisticsHelper::getCounterMetricsColumns();
// SQL part for the select sub-statement creates the SUM for each metrics column, and then connects them with ','
$selectSql = implode(', ', array_map(fn ($value): string => 'SUM(csd.' . $value . ')', $counterMetricsColumns));
$selectSubmissionDaily = DB::table('metrics_counter_submission_daily as csd')
->select(DB::raw("csd.context_id, csd.submission_id, {$monthFormatSql} as csdmonth, {$selectSql}"))
->whereRaw("{$monthFormatSql} = ?", [$month])
->groupBy(DB::raw('csd.context_id, csd.submission_id, csdmonth'));
DB::table('metrics_counter_submission_monthly')->insertUsing(array_merge(['context_id', 'submission_id', 'month'], $counterMetricsColumns), $selectSubmissionDaily);
$selectSubmissionInstitutionDaily = DB::table('metrics_counter_submission_institution_daily as csd')
->select(DB::raw("csd.context_id, csd.submission_id, csd.institution_id, {$monthFormatSql} as csdmonth, {$selectSql}"))
->whereRaw("{$monthFormatSql} = ?", [$month])
->groupBy(DB::raw('csd.context_id, csd.submission_id, csd.institution_id, csdmonth'));
DB::table('metrics_counter_submission_institution_monthly')->insertUsing(array_merge(['context_id', 'submission_id', 'institution_id', 'month'], $counterMetricsColumns), $selectSubmissionInstitutionDaily);
}
}
@@ -0,0 +1,89 @@
<?php
/**
* @file classes/services/queryBuilders/interfaces/EntityQueryBuilderInterface.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class EntityQueryBuilderInterface
*
* @ingroup services_query_builders
*
* @brief An interface that defines required methods for
* a QueryBuilder that retrieves one of the application's
* entities.
*/
namespace PKP\services\queryBuilders\interfaces;
use Illuminate\Database\Query\Builder;
interface EntityQueryBuilderInterface
{
/**
* Get a count of the number of rows that match the select
* conditions configured in this query builder.
*
* @return int
*/
public function getCount();
/**
* Get a list of ids that match the select conditions
* configured in this query builder.
*
* @return array
*/
public function getIds();
/**
* Get a query builder with the applied select, where and
* join clauses based on builder's configuration
*
* This returns an instance of Laravel's query builder.
*
* Call the `get` method on a query builder to return an array
* of matching rows.
*
* ```php
* $qb = new \PKP\services\queryBuilders\PublicationQueryBuilder();
* $result = $qb
* ->filterByContextIds(1)
* ->getQuery()
* ->get();
* ```
*
* Or use the query builder to retrieve objects from a DAO.
* This example retrieves the first 20 matching Publications.
*
* ```php
* $qo = $qb
* ->filterByContextIds(1)
* ->getQuery();
* $result = DAORegistry::getDAO('ReviewRoundDAO')->retrieveRange(
* $qo->toSql(),
* $qo->getBindings(),
* new DBResultRange(20, null, 0);
* );
* $queryResults = new DAOResultFactory($result, $reviewRoundDao, '_fromRow');
* $iteratorOfObjects = $queryResults->toIterator();
* ```
*
* Laravel's other query builder methods, such as `first`
* and `pluck`, can also be used.
*
* ```
* $qb = new \PKP\services\queryBuilders\PublicationQueryBuilder();
* $result = $qb
* ->filterByContextIds(1)
* ->getQuery()
* ->first();
* ```
*
* See: https://laravel.com/docs/5.5/queries
*
* @return Builder
*/
public function getQuery();
}