diff --git a/.htaccess b/.htaccess
index 6c370407..c7c01254 100755
--- a/.htaccess
+++ b/.htaccess
@@ -6,6 +6,13 @@
Order allow,deny
+
+ RewriteEngine On
+ # Protect hidden directory from vulnerability scanner
+ RewriteRule (^|/)\.([^/]+)(/|$) - [L,F]
+ RewriteRule (^|/)([^/]+)~(/|$) - [L,F]
+
+
# Don't show directory listings for URLs which map to a directory.
Options -Indexes
diff --git a/README.md b/README.md
index 3beee10d..fe4782f5 100755
--- a/README.md
+++ b/README.md
@@ -11,8 +11,10 @@ SLiMS is licensed under GNU GPL version 3. Please read "GPL-3.0 License.txt"
to learn more about GPL.
### System Requirements
-- PHP version 7.4;
+- PHP version 8.1;
- MySQL version 5.7 and or MariaDB version 10.3;
- PHP GD enabled
- PHP gettext enabled
-- PHP mbstring enabled
\ No newline at end of file
+- PHP mbstring enabled
+- PDO MySQL enabled
+- YAZ Optional
diff --git a/admin/admin_template/akasia-dz/index_template.inc.php b/admin/admin_template/akasia-dz/index_template.inc.php
index 3ee56264..4c2bb8dc 100644
--- a/admin/admin_template/akasia-dz/index_template.inc.php
+++ b/admin/admin_template/akasia-dz/index_template.inc.php
@@ -86,7 +86,15 @@
+
table_attr = 'class="s-table table table-sm table-bordered"';
- $reportgrid->setSQLColumn('i.item_code AS \''.__('Item Code').'\'',
- 'b.title AS \''.__('Title').'\'',
- 'ct.coll_type_name AS \''.__('Collection Type').'\'',
- 'i.item_status_id AS \''.__('Item Status').'\'',
- 'b.call_number AS \''.__('Call Number').'\'', 'i.biblio_id');
+ $reportgrid->setSQLColumn(
+ 'i.item_code AS \'' . __('Item Code') . '\'',
+ 'b.title AS \'' . __('Title') . '\'',
+ 'ct.coll_type_name AS \'' . __('Collection Type') . '\'',
+ 'i.item_status_id AS \'' . __('Item Status') . '\'',
+ 'b.call_number AS \'' . __('Call Number') . '\'',
+ 'i.biblio_id'
+ );
$reportgrid->setSQLorder('b.title ASC');
// is there any search
$criteria = 'b.biblio_id IS NOT NULL ';
- if (isset($_GET['title']) AND !empty($_GET['title'])) {
+ if (isset($_GET['title']) and !empty($_GET['title'])) {
$keyword = $dbs->escape_string(trim($_GET['title']));
$words = explode(' ', $keyword);
if (count($words) > 1) {
@@ -176,17 +182,17 @@
$concat_sql .= ') ';
$criteria .= $concat_sql;
} else {
- $criteria .= ' AND (b.title LIKE \'%'.$keyword.'%\' OR b.isbn_issn LIKE \'%'.$keyword.'%\')';
+ $criteria .= ' AND (b.title LIKE \'%' . $keyword . '%\' OR b.isbn_issn LIKE \'%' . $keyword . '%\')';
}
}
- if (isset($_GET['itemCode']) AND !empty($_GET['itemCode'])) {
+ if (isset($_GET['itemCode']) and !empty($_GET['itemCode'])) {
$item_code = $dbs->escape_string(trim($_GET['itemCode']));
- $criteria .= ' AND i.item_code LIKE \'%'.$item_code.'%\'';
+ $criteria .= ' AND i.item_code LIKE \'%' . $item_code . '%\'';
}
if (isset($_GET['collType'])) {
$coll_type_IDs = '';
foreach ($_GET['collType'] as $id) {
- $id = (integer)$id;
+ $id = (int)$id;
if ($id) {
$coll_type_IDs .= "$id,";
}
@@ -196,10 +202,10 @@
$criteria .= " AND i.coll_type_id IN($coll_type_IDs)";
}
}
- if (isset($_GET['gmd']) AND !empty($_GET['gmd'])) {
+ if (isset($_GET['gmd']) and !empty($_GET['gmd'])) {
$gmd_IDs = '';
foreach ($_GET['gmd'] as $id) {
- $id = (integer)$id;
+ $id = (int)$id;
if ($id) {
$gmd_IDs .= "$id,";
}
@@ -209,25 +215,25 @@
$criteria .= " AND b.gmd_id IN($gmd_IDs)";
}
}
- if (isset($_GET['status']) AND $_GET['status']!='0') {
+ if (isset($_GET['status']) and $_GET['status'] != '0') {
$status = $dbs->escape_string(trim($_GET['status']));
- $criteria .= ' AND i.item_status_id=\''.$status.'\'';
+ $criteria .= ' AND i.item_status_id=\'' . $status . '\'';
}
- if (isset($_GET['class']) AND ($_GET['class'] != '')) {
+ if (isset($_GET['class']) and ($_GET['class'] != '')) {
$class = $dbs->escape_string($_GET['class']);
- $criteria .= ' AND b.classification LIKE \''.$class.'%\'';
+ $criteria .= ' AND b.classification LIKE \'' . $class . '%\'';
}
- if (isset($_GET['location']) AND !empty($_GET['location'])) {
+ if (isset($_GET['location']) and !empty($_GET['location'])) {
$location = $dbs->escape_string(trim($_GET['location']));
- $criteria .= ' AND i.location_id=\''.$location.'\'';
+ $criteria .= ' AND i.location_id=\'' . $location . '\'';
}
- if (isset($_GET['publishYear']) AND !empty($_GET['publishYear'])) {
+ if (isset($_GET['publishYear']) and !empty($_GET['publishYear'])) {
$publish_year = $dbs->escape_string(trim($_GET['publishYear']));
- $criteria .= ' AND b.publish_year LIKE \'%'.$publish_year.'%\'';
+ $criteria .= ' AND b.publish_year LIKE \'%' . $publish_year . '%\'';
}
if (isset($_GET['recsEachPage'])) {
- $recsEachPage = (integer)$_GET['recsEachPage'];
- $num_recs_show = ($recsEachPage >= 20 && $recsEachPage <= 200)?$recsEachPage:$num_recs_show;
+ $recsEachPage = (int)$_GET['recsEachPage'];
+ $num_recs_show = ($recsEachPage >= 20 && $recsEachPage <= 200) ? $recsEachPage : $num_recs_show;
}
$reportgrid->setSQLCriteria($criteria);
@@ -242,21 +248,21 @@ function showTitleAuthors($obj_db, $array_data)
$_biblio_q = $obj_db->query('SELECT b.title, a.author_name FROM biblio AS b
LEFT JOIN biblio_author AS ba ON b.biblio_id=ba.biblio_id
LEFT JOIN mst_author AS a ON ba.author_id=a.author_id
- WHERE b.biblio_id='.$array_data[5]);
+ WHERE b.biblio_id=' . $array_data[5]);
$_authors = '';
while ($_biblio_d = $_biblio_q->fetch_row()) {
$_title = $_biblio_d[0];
- $_authors .= $_biblio_d[1].' - ';
+ $_authors .= $_biblio_d[1] . ' - ';
}
$_authors = substr_replace($_authors, '', -3);
- $_output = $_title.' '.$_authors.''."\n";
+ $_output = $_title . ' ' . $_authors . '' . "\n";
return $_output;
}
function showStatus($obj_db, $array_data)
{
$output = __('Available');
- $q = $obj_db->query('SELECT item_status_name FROM mst_item_status WHERE item_status_id=\''.$array_data[3].'\'');
- if(!empty($q->num_rows)){
+ $q = $obj_db->query('SELECT item_status_name FROM mst_item_status WHERE item_status_id=\'' . $array_data[3] . '\'');
+ if (!empty($q->num_rows)) {
$d = $q->fetch_row();
$s = $d[0];
$output = $s;
@@ -269,14 +275,28 @@ function showStatus($obj_db, $array_data)
$reportgrid->modifyColumnContent(3, 'callback{showStatus}');
$reportgrid->invisible_fields = array(5);
+ // show spreadsheet export button
+ $reportgrid->show_spreadsheet_export = true;
+
// put the result into variables
echo $reportgrid->createDataGrid($dbs, $table_spec, $num_recs_show);
- echo '';
+ $xlsquery = "SELECT i.item_code AS '" . __('Item Code') . "',
+ b.title AS '" . __('Title') . "',
+ ct.coll_type_name AS '" . __('Collection Type') . "',
+ i.item_status_id AS '" . __('Item Status') . "',
+ b.call_number AS '" . __('Call Number') . "' FROM " .
+ $table_spec . " WHERE " . $criteria;
+ // echo $xlsquery;
+ unset($_SESSION['xlsdata']);
+ $_SESSION['xlsquery'] = $xlsquery;
+ $_SESSION['tblout'] = "title_list_item";
+
$content = ob_get_clean();
// include the page template
- require SB.'/admin/'.$sysconf['admin_template']['dir'].'/printed_page_tpl.php';
+ require SB . '/admin/' . $sysconf['admin_template']['dir'] . '/printed_page_tpl.php';
}
diff --git a/admin/modules/system/app_user.php b/admin/modules/system/app_user.php
index 6ff5be94..ab99f29d 100755
--- a/admin/modules/system/app_user.php
+++ b/admin/modules/system/app_user.php
@@ -74,6 +74,8 @@ function getUserType($obj_db, $array_data, $col) {
if ($query_image->num_rows > 0) {
$_delete = $dbs->query(sprintf('UPDATE user SET user_image=NULL WHERE user_id=%d', $_POST['uimg']));
if ($_delete) {
+ // Change upict
+ $_SESSION['upict'] = 'person.png';
$postImage = stripslashes($_POST['img']);
$postImage = str_replace('/', '', $postImage);
@unlink(sprintf(IMGBS.'persons/%s', $postImage));
@@ -177,6 +179,8 @@ function getUserType($obj_db, $array_data, $col) {
// upload status alert
if (isset($upload_status)) {
if ($upload_status == UPLOAD_SUCCESS) {
+ // Change upict
+ $_SESSION['upict'] = $data['user_image'];
// write log
utility::writeLogs($dbs, 'staff', $_SESSION['uid'], 'system/user', $_SESSION['realname'].' upload image file '.$upload->new_filename, 'User image', 'Upload');
utility::jsAlert(__('Image Uploaded Successfully'));
@@ -200,6 +204,8 @@ function getUserType($obj_db, $array_data, $col) {
// upload status alert
if (isset($upload_status)) {
if ($upload_status == UPLOAD_SUCCESS) {
+ // Change upict
+ $_SESSION['upict'] = $data['user_image'];
// write log
utility::writeLogs($dbs, 'staff', $_SESSION['uid'], 'system/user', $_SESSION['realname'].' upload image file '.$upload->new_filename, 'User image', 'Upload');
utility::jsAlert(__('Image Uploaded Successfully'));
diff --git a/admin/modules/system/backup.php b/admin/modules/system/backup.php
index c7e6fc09..d017eeaf 100755
--- a/admin/modules/system/backup.php
+++ b/admin/modules/system/backup.php
@@ -123,19 +123,53 @@
?>
-
+
+
+
+
-
-
+
+
setSQLColumn('bl.backup_log_id',
- 'u.realname AS \''.__('Backup Executor').'\'',
- 'bl.backup_time AS \''.__('Backup Time').'\'',
- 'bl.backup_file AS \''.__('Backup File Location').'\'',
- 'bl.backup_file AS \''.__('File Size').'\'');
- $datagrid->setSQLorder('backup_time DESC');
- $datagrid->modifyColumnContent(4, 'callback{showFileSize}');
-}else{
- $datagrid->setSQLColumn(
- 'u.realname AS \''.__('Backup Executor').'\'',
+$datagrid->setSQLColumn('bl.backup_log_id',
+ 'u.realname AS \''.__('Backup Executor').'\'',
'bl.backup_time AS \''.__('Backup Time').'\'',
'bl.backup_file AS \''.__('Backup File Location').'\'',
- 'bl.backup_file AS \''.__('File Size').'\'');
- $datagrid->setSQLorder('backup_time DESC');
- $datagrid->modifyColumnContent(3, 'callback{showFileSize}');
-}
+ 'bl.backup_file AS \''.__('File Size').'\'');
+$datagrid->setSQLorder('backup_time DESC');
+$datagrid->modifyColumnContent(4, 'callback{showFileSize}');
+if (!$can_write) $datagrid->invisible_fields = [0];
+
// is there any search
if (isset($_GET['keywords']) AND $_GET['keywords']) {
$keywords = $dbs->escape_string($_GET['keywords']);
diff --git a/admin/modules/system/backup_proc.php b/admin/modules/system/backup_proc.php
index 098b092a..50e14571 100755
--- a/admin/modules/system/backup_proc.php
+++ b/admin/modules/system/backup_proc.php
@@ -47,10 +47,45 @@
die('
'.__('You don\'t have enough privileges to view this section').'
');
}
+function color($message, $type = 'white')
+{
+ switch ($type) {
+ case 'success':
+ $color = 'green';
+ break;
+
+ case 'info':
+ $color = 'blue';
+ break;
+
+ case 'error':
+ $color = 'red';
+ break;
+
+ default:
+ $color = 'white';
+ break;
+ }
+
+ return '' . $message . '';
+}
+
+function outputWithFlush($message)
+{
+ if (isset($_POST['verbose']) && $_POST['verbose'] == 'yes')
+ {
+ echo str_replace('\n', ' ', $message) . ' ';
+ ob_flush();
+ flush();
+ }
+}
+
// if backup process is invoked
if (isset($_POST['start']) && isset($_POST['tkn']) && $_POST['tkn'] === $_SESSION['token']) {
+ outputWithFlush(color('Starting...'));
sleep(2);
$output = '';
+ $error = false;
// turn on implicit flush
ob_implicit_flush();
@@ -97,12 +132,15 @@
@chdir($sysconf['backup_dir']);
// compress the backup using tar gz
if(function_exists('exec')){
+ outputWithFlush(color('Compressing...'));
+ sleep(1);
exec('tar cvzf backup_'.$time2append.'.sql.tar.gz backup_'.$time2append.'.sql', $outputs, $status);
if ($status == COMMAND_SUCCESS) {
// delete the original file
@unlink($data['backup_file']);
$output .= __("File is compressed using tar gz archive format");
$data['backup_file'] = $dbs->escape_string($sysconf['backup_dir'].'backup_'.$time2append.'.sql.tar.gz');
+ outputWithFlush(color('Compressing Success', 'success'));
}
}
// return to previous PHP working dir
@@ -111,18 +149,27 @@
// input log to database
$sql_op = new simbio_dbop($dbs);
$sql_op->insert('backup_log', $data);
+ outputWithFlush(color($output, 'success'));
} catch (\Exception $e) {
+ $error = true;
$output = sprintf(__('Backup FAILED!,\n%s'),$e->getMessage());
+ outputWithFlush(color($output, 'error'));
}
} else {
- $output = __("Backup FAILED! The Backup directory is not exists or not writeable");
+ $error = true;
+ $output = __("Backup FAILED! The Backup directory is not exists or not writeable") . ' ';
$output .= __("Contact System Administrator for the right path of backup directory");
+ outputWithFlush(color($output, 'error'));
}
// remove token
unset($_SESSION['token']);
- echo '';
- echo '';
+
+ if ($_POST['verbose'] == 'no')
+ {
+ utility::jsToastr(__('Backup'), $dbs->escape_string(strip_tags($output)), ($error ? 'error' : 'success'));
+ }
+ echo '';
exit();
}
diff --git a/admin/modules/system/biblio_indexer.inc.php b/admin/modules/system/biblio_indexer.inc.php
index 24fc0b27..138bafe7 100755
--- a/admin/modules/system/biblio_indexer.inc.php
+++ b/admin/modules/system/biblio_indexer.inc.php
@@ -139,7 +139,7 @@ public function makeIndex($int_biblio_id) {
$data['publish_place'] = $this->obj_db->escape_string($rb_id['publish_place']);
$data['isbn_issn'] = $this->obj_db->escape_string($rb_id['isbn_issn']);
$data['language'] = $this->obj_db->escape_string($rb_id['language']);
- $data['publish_year'] = $rb_id['publish_year'];
+ $data['publish_year'] = $this->obj_db->escape_string($rb_id['publish_year']);
$data['classification'] = $this->obj_db->escape_string($rb_id['classification']);
$data['spec_detail_info'] = $this->obj_db->escape_string($rb_id['spec_detail_info']);
$data['call_number'] = $this->obj_db->escape_string($rb_id['call_number']);
diff --git a/admin/modules/system/envinfo.php b/admin/modules/system/envinfo.php
index abd4e1a1..54b79058 100644
--- a/admin/modules/system/envinfo.php
+++ b/admin/modules/system/envinfo.php
@@ -47,6 +47,7 @@
// require SIMBIO.'simbio_DB/simbio_dbop.inc.php';
$environment = array(
+ array('title' => __('SLiMS Environment Mode'), 'desc' => ucfirst(ENVIRONMENT)),
array('title' => __('SLiMS Version'), 'desc' => SENAYAN_VERSION_TAG),
array('title' => __('Operating System'), 'desc' => php_uname('a')),
array('title' => __('OS Architecture'), 'desc' => php_uname('m').' '.(8 * PHP_INT_SIZE).' bit'),
diff --git a/admin/modules/system/envsetting.php b/admin/modules/system/envsetting.php
new file mode 100644
index 00000000..41b24ce1
--- /dev/null
+++ b/admin/modules/system/envsetting.php
@@ -0,0 +1,145 @@
+setTimeout(() => {parent.$(\'#mainContent\').simbioAJAX(\'' . $_SERVER['PHP_SELF'] . '\')}, 5000)';
+ // utility::jsAlert(json_encode($write));
+ exit;
+}
+
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+submit_button_attr = 'name="saveData" value="'.__('Save Settings').'" class="btn btn-default"';
+
+// form table attributes
+$form->table_attr = 'id="dataList" class="s-table table"';
+$form->table_header_attr = 'class="alterCell font-weight-bold"';
+$form->table_content_attr = 'class="alterCell2"';
+
+// Your Environment
+if ($BasedIp)
+{
+ $thisEnv = ucfirst((!in_array(getCurrentIp(), $RangeIp) ? $Environment : $ConditionEnvironment));
+ $HTML = <<{$thisEnv}
+ HTML;
+ $form->addAnything('Your Environment Mode', $HTML);
+
+ $Environment = $ConditionEnvironment;
+}
+
+// Environment List
+$EnvOptions = [
+ [1, __('Production')],
+ [0, __('Development')]
+ ];
+$form->addSelectList('env', __('System Environment Mode'), $EnvOptions, ( $Environment == 'production' ? 1 : 0 ) ,'class="form-control col-3"');
+$BasedIpOptions = [
+ [0, __('Disable')],
+ [1, __('Enable')]
+];
+$form->addSelectList('basedIp', __('Environment for some IP?'), $BasedIpOptions, ( $BasedIp ? 1 : 0 ) ,'class="form-control col-3"');
+$form->addTextField('textarea', 'rangeIp', __('Range Ip wil be impacted with Environment. Example : 10.120.33.40;20.100.34.10. '), implode(';', $RangeIp), 'style="margin-top: 0px; margin-bottom: 0px; height: 149px;" class="form-control" placeholder="Leave it empty, if you want to set environment to impact for all IP"');
+// print out the object
+echo $form->printOut();
\ No newline at end of file
diff --git a/admin/modules/system/holiday.php b/admin/modules/system/holiday.php
index 80873180..822cdc74 100755
--- a/admin/modules/system/holiday.php
+++ b/admin/modules/system/holiday.php
@@ -55,12 +55,12 @@
// check form validity
$holDesc = trim($dbs->escape_string(strip_tags($_POST['holDesc'])));
if (empty($holDesc)) {
- utility::jsAlert(__('Holiday description can\'t be empty!'));
+ utility::jsToastr(__('Holiday Settings'),__('Holiday description can\'t be empty!'),'warning');
exit();
} else {
$data['holiday_date'] = trim($_POST['holDate']); // remove extra whitespace
if(!preg_match('@^[0-9]{4}-[0-9]{2}-[0-9]{2}$@', $data['holiday_date'])) {
- utility::jsAlert(__('Holiday Date Start must have the format YYYY-MM-DD!'));
+ utility::jsToastr(__('Holiday Settings'),__('Holiday Date Start must have the format YYYY-MM-DD!'),'warning');
exit();
}
$holiday_start_date = $data['holiday_date'];
@@ -75,28 +75,28 @@
$updateRecordID = (integer)$_POST['updateRecordID'];
if ($sql_op->update('holiday', $data, 'holiday_id='.$updateRecordID)) {
utility::writeLogs($dbs, 'staff', $_SESSION['uid'], 'System', $_SESSION['realname'].' update holiday date for '.$data['description'], 'Holiday', 'Update');
- utility::jsAlert(__('Holiday Data Successfully updated'));
+ utility::jsToastr(__('Holiday Settings'),__('Holiday Data Successfully updated'),'success');
// update holiday_dayname session
$_SESSION['holiday_date'][$data['holiday_date']] = $data['holiday_date'];
echo '';
exit();
} else {
utility::writeLogs($dbs, 'staff', $_SESSION['uid'], 'System', $_SESSION['realname'].' failed update holiday data for '.$data['description'], 'Holiday', 'Fail');
- utility::jsAlert(__('Holiday FAILED to update. Please Contact System Administrator')."\n".$sql_op->error);
+ utility::jsToastr(__('Holiday Settings'),__('Holiday FAILED to update. Please Contact System Administrator')."\n".$sql_op->error,'error');
}
} else {
/* INSERT RECORD MODE */
// insert the data
if ($sql_op->insert('holiday', $data)) {
utility::writeLogs($dbs, 'staff', $_SESSION['uid'], 'System', $_SESSION['realname'].' add holiday date for '.$data['description'], 'Holiday', 'Add');
- utility::jsAlert(__('New Holiday Successfully Saved'));
+ utility::jsToastr(__('Holiday Settings'),__('New Holiday Successfully Saved'),'success');
// update holiday_dayname session
$_SESSION['holiday_date'][$data['holiday_date']] = $data['holiday_date'];
// date range insert
if (!empty($_POST['holDateEnd'])) {
$holiday_end_date = trim($_POST['holDateEnd']); // remove extra whitespace
if(!preg_match('@^[0-9]{4}-[0-9]{2}-[0-9]{2}$@', $holiday_end_date)) {
- utility::jsAlert(__('Holiday Date End must have the format YYYY-MM-DD if it is not empty!'));
+ utility::jsToastr(__('Holiday Settings'),__('Holiday Date End must have the format YYYY-MM-DD if it is not empty!'),'warning');
exit();
}
// check if holiday end date is more than holiday start date
@@ -121,7 +121,7 @@
exit();
} else {
utility::writeLogs($dbs, 'staff', $_SESSION['uid'], 'System', $_SESSION['realname'].' failed to add holiday data for '.$data['description'], 'Holiday', 'Fail');
- utility::jsAlert(__('Holiday FAILED to Save. Please Contact System Administrator')."\n".$sql_op->error);
+ utility::jsToastr(__('Holiday Settings'),__('Holiday FAILED to Save. Please Contact System Administrator')."\n".$sql_op->error,'error');
}
}
}
@@ -157,10 +157,10 @@
// error alerting
if ($error_num == 0) {
utility::writeLogs($dbs, 'staff', $_SESSION['uid'], 'System', $_SESSION['realname'].' remove holiday date with id '.$_log, 'Holiday', 'Delete');
- utility::jsAlert(__('All Data Successfully Deleted'));
+ utility::jsToastr(__('Holiday Settings'),__('All Data Successfully Deleted'),'success');
echo '';
} else {
- utility::jsAlert(__('Some or All Data NOT deleted successfully!\nPlease contact system administrator'));
+ utility::jsToastr(__('Holiday Settings'),__('Some or All Data NOT deleted successfully!\nPlease contact system administrator'),'warning');
echo '';
}
exit();
@@ -170,16 +170,15 @@
?>
-
-
+
+
-
- .
-
+
+
-
+
@@ -254,6 +253,36 @@
$datagrid->setSQLCriteria('holiday_date IS NOT NULL');
}
+ $datagrid->modifyColumnContent(1, 'callback{replaceDayname}');
+
+ function replaceDayname($obj_db, $array_data){
+ switch ($array_data[1]) {
+ case 'Sun':
+ $dayname = __('Sunday');
+ break;
+ case 'Mon':
+ $dayname = __('Monday');
+ break;
+ case 'Tue':
+ $dayname = __('Tuesday');
+ break;
+ case 'Wed':
+ $dayname = __('Wednesday');
+ break;
+ case 'Thu':
+ $dayname = __('Thursday');
+ break;
+ case 'Fri':
+ $dayname = __('Friday');
+ break;
+ case 'Sat':
+ $dayname = __('Saturday');
+ break;
+ default:
+ $dayname = $array_data[1];
+ }
+ return $dayname;
+ }
// set table and table header attributes
$datagrid->icon_edit = SWB.'admin/'.$sysconf['admin_template']['dir'].'/'.$sysconf['admin_template']['theme'].'/edit.gif';
$datagrid->table_attr = 'id="dataList" class="s-table table"';
@@ -292,7 +321,11 @@
echo '
'.__('Holiday settings saved').'
';
}
}
- }
+ // remove all the holiday from holiday setting and emptying session
+} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $_SESSION['holiday_dayname'] = array();
+ $dbs->query('DELETE FROM holiday WHERE holiday_date IS NULL');
+}
// get holiday data from database
$rec_q = $dbs->query('SELECT DISTINCT holiday_dayname FROM holiday WHERE holiday_date IS NULL');
diff --git a/admin/modules/system/index.php b/admin/modules/system/index.php
index b297f8f6..335f442e 100755
--- a/admin/modules/system/index.php
+++ b/admin/modules/system/index.php
@@ -22,6 +22,9 @@
/* Global application configuration */
// key to authenticate
+use SLiMS\SearchEngine\DefaultEngine;
+use SLiMS\SearchEngine\Engine;
+
if (!defined('INDEX_AUTH')) {
define('INDEX_AUTH', '1');
}
@@ -146,6 +149,12 @@ function addOrUpdateSetting($name, $value) {
// language
$dbs->query('UPDATE setting SET setting_value=\''.$dbs->escape_string(serialize($_POST['default_lang'])).'\' WHERE setting_name=\'default_lang\'');
+ // timezone
+ addOrUpdateSetting('timezone', utility::filterData('timezone', 'post', true, true, true));
+
+ // search engine
+ addOrUpdateSetting('search_engine', utility::filterData('search_engine', 'post', true, true, true));
+
// opac num result
$dbs->query('UPDATE setting SET setting_value=\''.$dbs->escape_string(serialize($_POST['opac_result_num'])).'\' WHERE setting_name=\'opac_result_num\'');
@@ -308,6 +317,15 @@ function addOrUpdateSetting($name, $value) {
require_once(LANG.'localisation.php');
$form->addSelectList('default_lang', __('Default App. Language'), $available_languages, $sysconf['default_lang'], 'class="form-control col-3"');
+// timezone
+$html = '';
+$html .= '' . __('List of timezones supported by PHP') . '';
+$form->addAnything(__('Default App. Timezone'), $html);
+
+// search engine
+$engine = array_map(fn($e) => [$e, $e], Engine::init()->get());
+$form->addSelectList('search_engine', __('Search Engine'), $engine, $sysconf['search_engine'] ?? DefaultEngine::class, 'class="select2 col-md-6"');
+
// opac result list number
$result_num_options[] = array('10', '10');
$result_num_options[] = array('20', '20');
diff --git a/admin/modules/system/mailsetting.php b/admin/modules/system/mailsetting.php
new file mode 100644
index 00000000..043e648e
--- /dev/null
+++ b/admin/modules/system/mailsetting.php
@@ -0,0 +1,235 @@
+ $value)
+ {
+ $key = str_replace('_', '', $key);
+ $customConfig = ($_POST[$key]) ?? '?';
+ $Config = str_replace('_' . $key . '_', $customConfig, $Config);
+ }
+
+ // write mail configuration
+ file_put_contents($configPath, $Config);
+
+ // alert
+ utility::jsToastr(__('E-Mail Configuration'), __('Settings inserted.'), 'success');
+
+ // redirect
+ echo '';
+ exit;
+}
+
+if (isset($_POST['testMail']))
+{
+ include_once $configPath;
+ $mail = new PHPMailer(true);
+ try {
+ ob_start();
+ //Server settings
+ $mail->SMTPDebug = $sysconf['mail']['debug']; // Enable verbose debug output
+ $mail->isSMTP(); // Send using SMTP
+ $mail->Host = $sysconf['mail']['server']; // Set the SMTP server to send through
+ $mail->SMTPAuth = $sysconf['mail']['auth_enable']; // Enable SMTP authentication
+ $mail->Username = $sysconf['mail']['auth_username']; // SMTP username
+ $mail->Password = $sysconf['mail']['auth_password']; // SMTP password
+ if ($sysconf['mail']['SMTPSecure'] === 'tls') { // Enable TLS encryption; `PHPMailer::ENCRYPTION_SMTPS` encouraged
+ $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
+ } else if ($sysconf['mail']['SMTPSecure'] === 'ssl') {
+ $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
+ }
+ $mail->Port = $sysconf['mail']['server_port']; // TCP port to connect to, use 465 for `PHPMailer::ENCRYPTION_SMTPS` above
+
+ //Recipients
+ $mail->setFrom($sysconf['mail']['from'], $sysconf['mail']['from_name']);
+ $mail->addReplyTo($sysconf['mail']['reply_to'], $sysconf['mail']['reply_to_name']);
+ $mail->addAddress($_POST['receiveraddress'], $_POST['receivername']);
+
+ $mail->isHTML(true); // Set email format to HTML
+ $mail->Subject = 'SLiMS :: Outgoing Mail Testing';
+ $mail->msgHTML($_POST['dummyMessage']);
+ $mail->AltBody = strip_tags($_POST['dummyMessage']);
+
+ $mail->send();
+ $result = ob_get_clean();
+
+ utility::jsToastr(__('Success'), 'Mail testing has been sent', 'success');
+ } catch (Exception $exception) {
+ utility::jsToastr(__('Success'), 'Mail testing could not be sent. Mailer Error: ' . $mail->ErrorInfo, 'success');
+ }
+ exit;
+}
+
+// create new instance
+$form = new simbio_form_table_AJAX('mainForm', $_SERVER['PHP_SELF'], 'post');
+
+// form table attributes
+$form->table_attr = 'id="dataList" class="s-table table"';
+$form->table_header_attr = 'class="alterCell font-weight-bold"';
+$form->table_content_attr = 'class="alterCell2"';
+
+?>
+
+
+
+
+
+
+ ' . __('Directory config is not writeable.') . " ";
+ echo __('Make the following files and directories (and their contents) writeable (i.e., by changing the owner or permissions with chown or chmod)');
+ }
+ ?>
+
+
+
+submit_button_attr = 'name="saveData" value="'.__('Save Settings').'" class="btn btn-default"';
+
+// edit mode
+if ($isConfigExists) $form->addHidden('edit', true);
+
+// Debug
+$DebugOptions = [
+ [SMTP::DEBUG_OFF, __('Production')],
+ [SMTP::DEBUG_SERVER, __('Development')]
+];
+$form->addSelectList('debug', __('Environment'), $DebugOptions, (int)setValue('debug') ,'class="form-control col-3"');
+
+// SMTPSecure
+$form->addTextField('text', 'SMTPSecure', __('SMTP Encryption'), setValue('SMTPSecure'), 'style="width: 60%;" class="form-control" placeholder="'.setPlaceholder('SMTPSecure').'"');
+
+// Server
+$form->addTextField('text', 'server', __('SMTP Server address'), setValue('server'), 'style="width: 60%;" class="form-control" placeholder="'.setPlaceholder('server').'"');
+
+// Server Port
+$form->addTextField('text', 'serverport', __('SMTP Port'), setValue('server_port'), 'style="width: 60%;" class="form-control" placeholder="'.setPlaceholder('server_port').'"');
+
+// set field username
+$form->addTextField('text', 'authusername', __('Authentication Username'), setValue('auth_username'), 'style="width: 60%;" class="form-control" placeholder="'.setPlaceholder('auth_username').'"',);
+
+// set field password
+$form->addTextField('password', 'authpassword', __('Authentication Password'), '', 'style="width: 60%;" class="form-control" placeholder="'.setPlaceholder('auth_password').'"', __('Password does not appearance for security reason. If you want to look it, open sysconfig.mail.inc.php file.'));
+
+// set email sender address
+$form->addTextField('text', 'from', __('Email Sender Address'), setValue('from'), 'style="width: 60%;" class="form-control" placeholder="'.setPlaceholder('from').'"');
+
+// set email sender label
+$form->addTextField('text', 'fromname', __('Email Sender Label'), setValue('from_name'), 'style="width: 60%;" class="form-control" placeholder="'.setPlaceholder('from_name').'"');
+
+// test mail
+if ($isConfigExists)
+{
+ $BtnLabel = __('Do Test');
+ $Url = $_SERVER['PHP_SELF'] . '?section=test';
+ $html = <<
+
+ {$BtnLabel}
+
+ HTML;
+ $form->addAnything('Test Mail Configuration', $html);
+}
+// print out the object
+echo $form->printOut();
+// End main configuration
+else:
+// Dummy content
+$content = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book";
+// start test page
+$form->submit_button_attr = 'name="testMail" value="'.__('Send Mail').'" class="btn btn-default"';
+$form->addTextField('text', 'receivername', __('Receiver Name'),'', 'style="width: 60%;" class="form-control"');
+$form->addTextField('text', 'receiveraddress', __('Receiver Address'), '', 'style="width: 60%;" class="form-control"');
+$form->addTextField('textarea', 'dummyMessage', __('Dummy Content'), $content, 'style="margin-top: 0px; margin-bottom: 0px; height: 149px;" class="form-control"');
+// print out the object
+echo $form->printOut();
+// end test page
+endif;
+?>
+
\ No newline at end of file
diff --git a/admin/modules/system/plugins.php b/admin/modules/system/plugins.php
index 2a76cdaf..f96c50fb 100644
--- a/admin/modules/system/plugins.php
+++ b/admin/modules/system/plugins.php
@@ -124,7 +124,7 @@
// if have migration and version is different
// disable it.
- $version = (json_decode($plugin_actives[$hash]->options))->version ?? '';
+ $version = (json_decode($plugin_actives[$hash]->options??''))->version ?? '';
if ($version !== $plugin->version && $plugin->migration->is_exist) {
$enable_disable = __('Disabled');
$is_active = '';
diff --git a/admin/modules/system/submenu.php b/admin/modules/system/submenu.php
index 6f22836f..64235737 100755
--- a/admin/modules/system/submenu.php
+++ b/admin/modules/system/submenu.php
@@ -30,10 +30,12 @@
if ($_SESSION['uid'] == 1) {
$menu[] = array(__('System Configuration'), MWB.'system/index.php', __('Configure Global System Preferences'));
$menu[] = array(__('System Environment'), MWB.'system/envinfo.php', __('Information about System Environment'));
+ $menu[] = array(__('System Environment Setting'), MWB.'system/envsetting.php', __('Configure System Environment Mode'));
$menu[] = array(__('UCS Setting'), MWB.'system/ucsetting.php', __('Configure UCS Preferences'));
$menu[] = array(__('Theme'), MWB.'system/theme.php', __('Configure theme Preferences'));
$menu[] = array(__('Plugins'), MWB . 'system/plugins.php');
$menu[] = array(__('Custom Field'), MWB.'system/custom_field.php', __('Configure custom field'));
+ $menu[] = array(__('E-Mail Setting'), MWB.'system/mailsetting.php', __('Configure E-Mail Preferences'));
}
$menu[] = array(__('Content'), MWB.'system/content.php', __('Content'));
// only administrator have privileges for below menus
diff --git a/admin/modules/system/user_group.php b/admin/modules/system/user_group.php
index 5648e353..a81c8eed 100755
--- a/admin/modules/system/user_group.php
+++ b/admin/modules/system/user_group.php
@@ -232,7 +232,7 @@
while ($access_data = $rec_q->fetch_assoc()) {
$priv_data[$access_data['module_id']]['r'] = $access_data['r'];
$priv_data[$access_data['module_id']]['w'] = $access_data['w'];
- $priv_data[$access_data['module_id']]['menus'] = json_decode($access_data['menus']);
+ $priv_data[$access_data['module_id']]['menus'] = json_decode($access_data['menus'] ?? '{}');
}
$priv_table = '';
include 'module_priv_form_adv.inc.php';
diff --git a/api/v1/controllers/BiblioController.php b/api/v1/controllers/BiblioController.php
index d27f11d7..d952253c 100644
--- a/api/v1/controllers/BiblioController.php
+++ b/api/v1/controllers/BiblioController.php
@@ -41,7 +41,7 @@ public function getPopular()
FROM loan AS l
LEFT JOIN item AS i ON l.item_code=i.item_code
LEFT JOIN biblio AS b ON i.biblio_id=b.biblio_id
- WHERE b.title IS NOT NULL
+ WHERE b.title IS NOT NULL AND b.opac_hide < 1
GROUP BY b.biblio_id
ORDER BY total DESC
LIMIT {$limit}";
@@ -58,7 +58,7 @@ public function getPopular()
$need = $limit;
}
- $sql = "SELECT biblio_id, title, image FROM biblio ORDER BY last_update DESC LIMIT {$need}";
+ $sql = "SELECT biblio_id, title, image FROM biblio WHERE opac_hide < 1 ORDER BY last_update DESC LIMIT {$need}";
$query = $this->db->query($sql);
while ($data = $query->fetch_assoc()) {
$data['image'] = $this->getImagePath($data['image']);
@@ -74,7 +74,7 @@ public function getLatest() {
$limit = 6;
$sql = "SELECT biblio_id, title, image
- FROM biblio
+ FROM biblio WHERE opac_hide < 1
ORDER BY last_update DESC
LIMIT {$limit}";
@@ -90,7 +90,7 @@ public function getLatest() {
public function getTotalAll()
{
- $query = $this->db->query("SELECT COUNT(biblio_id) FROM biblio");
+ $query = $this->db->query("SELECT COUNT(biblio_id) FROM biblio WHERE opac_hide < 1");
parent::withJson([
'data' => ($query->fetch_row())[0]
]);
@@ -100,7 +100,7 @@ public function getByGmd($gmd) {
$limit = 3;
$sql = "SELECT b.biblio_id, b.title, b.image, b.notes
FROM biblio AS b, mst_gmd AS g
- WHERE b.gmd_id=g.gmd_id AND g.gmd_name='$gmd'
+ WHERE b.gmd_id=g.gmd_id AND g.gmd_name='$gmd' AND b.opac_hide < 1
ORDER BY b.last_update DESC
LIMIT {$limit}";
$query = $this->db->query($sql);
@@ -117,7 +117,7 @@ public function getByCollType($coll_type) {
$limit = 3;
$sql = "SELECT b.biblio_id, b.title, b.image, b.notes
FROM biblio AS b, item AS i, mst_coll_type AS c
- WHERE b.biblio_id=i.biblio_id AND i.coll_type_id=c.coll_type_id AND c.coll_type_name='$coll_type'
+ WHERE b.biblio_id=i.biblio_id AND i.coll_type_id=c.coll_type_id AND c.coll_type_name='$coll_type' AND b.opac_hide < 1
ORDER BY b.last_update DESC
LIMIT {$limit}";
$query = $this->db->query($sql);
diff --git a/api/v1/helpers/Cache.php b/api/v1/helpers/Cache.php
index e7f4897e..ce9b2d6e 100644
--- a/api/v1/helpers/Cache.php
+++ b/api/v1/helpers/Cache.php
@@ -32,4 +32,16 @@ static function get($name) {
}
return null;
}
+
+ /**
+ * @param $name string
+ * @return void
+ */
+ static function destroy($name)
+ {
+ $path = __DIR__ . '/../../../files/cache/cache_' . $name . '.json';
+ if (file_exists($path)) {
+ @unlink($path);
+ }
+ }
}
\ No newline at end of file
diff --git a/changes.txt b/changes.txt
index 97d197f7..a398ac00 100644
--- a/changes.txt
+++ b/changes.txt
@@ -44,6 +44,23 @@ Code enhancement: John Antony (john.antony@iamplus.com)
THANKS TO OUR USERS AND COMMUNITIES, YOU ARE DEVELOPERS TOO!
+SLiMS 9.5.0 (Bulian)
+========================================================================
+Added : Popup visitor report by day
+Added : Fines value in overdue list
+Added : Password field for encrypted attachment
+Updated : Check uncommon keyword to prevent sql injection
+Fixed : Unable to import biblio data on first header
+Fixed : Image path for Minigalnano
+Updated : Server for quote in Visitor Counter page
+Fixed : Ignore holiday fines calculation
+Fixed : Unable to download backup file result from user not admin
+Fixed : Holiday setting
+Fixed : Query when import biblio data header
+Fixed : Unrelated field after import item data from CSV
+Fixed : MARC export search
+Fixed : Prevent hidden biblio to show in home page
+
SLiMS 9.4.2 (Bulian)
========================================================================
Fixed : Stock take report detail
diff --git a/config/sysconfig.env.inc.php b/config/sysconfig.env.inc.php
new file mode 100644
index 00000000..79d4b722
--- /dev/null
+++ b/config/sysconfig.env.inc.php
@@ -0,0 +1,33 @@
+'.__('You are currently Logged on as member').': '.$_SESSION['m_name'].' ('.$_SESSION['m_email'].')'.__('LOGOUT').'
';
}
+// Load hook before content load
+SLiMS\Plugins::getInstance()->execute('before_content_load');
+
// start the output buffering for main content
ob_start();
require LIB.'contents/common.inc.php';
@@ -64,15 +69,8 @@
// some extra checking
$path = preg_replace('@^(http|https|ftp|sftp|file|smb):@i', '', $path);
$path = preg_replace('@\/@i','',$path);
- // check if the file exists
- if (file_exists(LIB.'contents/'.$path.'.inc.php')) {
- if ($path != 'show_detail') {
- $metadata = '';
- }
- include LIB.'contents/'.$path.'.inc.php';
- }
// check path from plugins
- elseif (isset(($menu = \SLiMS\Plugins::getInstance()->getMenus('opac'))[$path])) {
+ if (isset(($menu = \SLiMS\Plugins::getInstance()->getMenus('opac'))[$path])) {
if (file_exists($menu[$path][3])) {
$page_title = $menu[$path][0];
include $menu[$path][3];
@@ -80,6 +78,13 @@
// not found
http_response_code(404);
}
+ }
+ // check if the file exists
+ elseif (file_exists(LIB.'contents/'.$path.'.inc.php')) {
+ if ($path != 'show_detail') {
+ $metadata = '';
+ }
+ include LIB.'contents/'.$path.'.inc.php';
} else {
// get content data from database
$metadata = '';
@@ -116,5 +121,8 @@
// main content grab
$main_content = ob_get_clean();
+// Load hook after content load
+SLiMS\Plugins::getInstance()->execute('after_content_load');
+
// template output
require $sysconf['template']['dir'].'/'.$sysconf['template']['theme'].'/index_template.inc.php';
diff --git a/install/SLiMS.inc.php b/install/SLiMS.inc.php
index da7ec6a8..4abb3df9 100644
--- a/install/SLiMS.inc.php
+++ b/install/SLiMS.inc.php
@@ -50,6 +50,31 @@ function getBearerToken()
return null;
}
+ function phpExtensionCheck($returnType = 'html')
+ {
+ if ($returnType == 'bool')
+ {
+ // Minimum SLiMS PHP Extension requirement
+ return $this->isGdOk() && $this->isMbStringOk() && $this->isGettextOk() && $this->isPdoOk();
+ }
+
+ $message = '
+ A PHP library for representing and manipulating collections.
+
-ramsey/collection is a PHP 7.2+ library for representing and manipulating collections.
+
+
+
+
+
+
+
+
+
+
+## About
+
+ramsey/collection is a PHP library for representing and manipulating collections.
Much inspiration for this library came from the [Java Collections Framework][java].
@@ -16,7 +24,6 @@ This project adheres to a [code of conduct](CODE_OF_CONDUCT.md).
By participating in this project and its community, you are expected to
uphold this code.
-
## Installation
Install this package as a dependency using [Composer](https://getcomposer.org).
@@ -27,8 +34,6 @@ composer require ramsey/collection
## Usage
-The [latest class API documentation][apidocs] is available online.
-
Examples of how to use this framework can be found in the
[Wiki pages](https://github.com/ramsey/collection/wiki/Examples).
@@ -37,7 +42,7 @@ Examples of how to use this framework can be found in the
Contributions are welcome! Before contributing to this project, familiarize
yourself with [CONTRIBUTING.md](CONTRIBUTING.md).
-To develop this project, you will need [PHP](https://www.php.net) 7.2 or greater
+To develop this project, you will need [PHP](https://www.php.net) 7.3 or greater
and [Composer](https://getcomposer.org).
After cloning this repository locally, execute the following commands:
@@ -49,99 +54,22 @@ composer install
Now, you are ready to develop!
-### Tooling
-
-This project uses [CaptainHook](https://github.com/CaptainHookPhp/captainhook)
-to validate all staged changes prior to commit.
-
-#### Composer Commands
-
-To see all the commands available in the project `br` namespace for
-Composer, type:
-
-``` bash
-composer list br
-```
-
-##### Composer Command Autocompletion
-
-If you'd like to have Composer command auto-completion, you may use
-[bamarni/symfony-console-autocomplete](https://github.com/bamarni/symfony-console-autocomplete).
-Install it globally with Composer:
-
-``` bash
-composer global require bamarni/symfony-console-autocomplete
-```
-
-Then, in your shell configuration file — usually `~/.bash_profile` or `~/.zshrc`,
-but it could be different depending on your settings — ensure that your global
-Composer `bin` directory is in your `PATH`, and evaluate the
-`symfony-autocomplete` command. This will look like this:
-
-``` bash
-export PATH="$(composer config home)/vendor/bin:$PATH"
-eval "$(symfony-autocomplete)"
-```
+## Coordinated Disclosure
-Now, you can use the `tab` key to auto-complete Composer commands:
-
-``` bash
-composer br:[TAB][TAB]
-```
-
-#### Coding Standards
-
-This project follows a superset of [PSR-12](https://www.php-fig.org/psr/psr-12/)
-coding standards, enforced by [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer).
-The project PHP_CodeSniffer configuration may be found in `phpcs.xml.dist`.
-
-CaptainHook will run PHP_CodeSniffer before committing. It will attempt to fix
-any errors it can, and it will reject the commit if there are any un-fixable
-issues. Many issues can be fixed automatically and will be done so pre-commit.
-
-You may lint the entire codebase using PHP_CodeSniffer with the following
-commands:
-
-``` bash
-# Lint
-composer br:lint
-
-# Lint and autofix
-composer br:lint:fix
-```
-
-#### Static Analysis
-
-This project uses a combination of [PHPStan](https://github.com/phpstan/phpstan)
-and [Psalm](https://github.com/vimeo/psalm) to provide static analysis of PHP
-code. Configurations for these are in `phpstan.neon.dist` and `psalm.xml`,
-respectively.
-
-CaptainHook will run PHPStan and Psalm before committing. The pre-commit hook
-does not attempt to fix any static analysis errors. Instead, the commit will
-fail, and you must fix the errors manually.
-
-You may run static analysis manually across the whole codebase with the
-following command:
-
-``` bash
-# Static analysis
-composer br:analyze
-```
+Keeping user information safe and secure is a top priority, and we welcome the
+contribution of external security researchers. If you believe you've found a
+security issue in software that is maintained in this repository, please read
+[SECURITY.md][] for instructions on submitting a vulnerability report.
-### Project Structure
+## ramsey/collection for Enterprise
-This project uses [pds/skeleton](https://github.com/php-pds/skeleton) as its
-base folder structure and layout.
+Available as part of the Tidelift Subscription.
-| Name | Description |
-| ------------------| ---------------------------------------------- |
-| **bin/** | Commands and scripts for this project |
-| **build/** | Cache, logs, reports, etc. for project builds |
-| **docs/** | Project-specific documentation |
-| **resources/** | Additional resources for this project |
-| **src/** | Project library and application source code |
-| **tests/** | Tests for this project |
+The maintainers of ramsey/collection and thousands of other packages are working
+with Tidelift to deliver commercial support and maintenance for the open source
+packages you use to build your applications. Save time, reduce risk, and improve
+code health, while paying the maintainers of the exact packages you use.
+[Learn more.](https://tidelift.com/subscription/pkg/packagist-ramsey-collection?utm_source=undefined&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
## Copyright and License
@@ -151,20 +79,4 @@ MIT License (MIT). Please see [LICENSE](LICENSE) for more information.
[java]: http://docs.oracle.com/javase/8/docs/technotes/guides/collections/index.html
-[apidocs]: https://docs.benramsey.com/ramsey-collection/latest/
-
-[badge-source]: http://img.shields.io/badge/source-ramsey/collection-blue.svg?style=flat-square
-[badge-release]: https://img.shields.io/packagist/v/ramsey/collection.svg?style=flat-square&label=release
-[badge-license]: https://img.shields.io/packagist/l/ramsey/collection.svg?style=flat-square
-[badge-php]: https://img.shields.io/packagist/php-v/ramsey/collection.svg?style=flat-square
-[badge-build]: https://img.shields.io/travis/ramsey/collection/master.svg?style=flat-square
-[badge-coverage]: https://img.shields.io/coveralls/github/ramsey/collection/master.svg?style=flat-square
-[badge-downloads]: https://img.shields.io/packagist/dt/ramsey/collection.svg?style=flat-square&colorB=mediumvioletred
-
-[source]: https://github.com/ramsey/collection
-[packagist]: https://packagist.org/packages/ramsey/collection
-[license]: https://github.com/ramsey/collection/blob/master/LICENSE
-[php]: https://php.net
-[build]: https://travis-ci.org/ramsey/collection
-[coverage]: https://coveralls.io/r/ramsey/collection?branch=master
-[downloads]: https://packagist.org/packages/ramsey/collection
+[security.md]: https://github.com/ramsey/collection/blob/master/SECURITY.md
diff --git a/lib/collection/SECURITY.md b/lib/collection/SECURITY.md
new file mode 100644
index 00000000..b052f3b6
--- /dev/null
+++ b/lib/collection/SECURITY.md
@@ -0,0 +1,113 @@
+
+
+# Vulnerability Disclosure Policy
+
+Keeping user information safe and secure is a top priority, and we welcome the
+contribution of external security researchers.
+
+## Scope
+
+If you believe you've found a security issue in software that is maintained in
+this repository, we encourage you to notify us.
+
+| Version | In scope | Source code |
+| :-----: | :------: | :---------- |
+| latest | ✅ | https://github.com/ramsey/collection |
+
+## How to Submit a Report
+
+To submit a vulnerability report, please contact us at .
+Your submission will be reviewed and validated by a member of our team.
+
+## Safe Harbor
+
+We support safe harbor for security researchers who:
+
+* Make a good faith effort to avoid privacy violations, destruction of data, and
+ interruption or degradation of our services.
+* Only interact with accounts you own or with explicit permission of the account
+ holder. If you do encounter Personally Identifiable Information (PII) contact
+ us immediately, do not proceed with access, and immediately purge any local
+ information.
+* Provide us with a reasonable amount of time to resolve vulnerabilities prior
+ to any disclosure to the public or a third-party.
+
+We will consider activities conducted consistent with this policy to constitute
+"authorized" conduct and will not pursue civil action or initiate a complaint to
+law enforcement. We will help to the extent we can if legal action is initiated
+by a third party against you.
+
+Please submit a report to us before engaging in conduct that may be inconsistent
+with or unaddressed by this policy.
+
+## Preferences
+
+* Please provide detailed reports with reproducible steps and a clearly defined
+ impact.
+* Include the version number of the vulnerable package in your report
+* Social engineering (e.g. phishing, vishing, smishing) is prohibited.
+
+## Encryption Key for security@ramsey.dev
+
+For increased privacy when reporting sensitive issues, you may encrypt your
+messages using the following key:
+
+```
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBF+Z9gEBEACbT/pIx8RR0K18t8Z2rDnmEV44YdT7HNsMdq+D6SAlx8UUb6AU
+jGIbV9dgBgGNtOLU1pxloaJwL9bWIRbj+X/Qb2WNIP//Vz1Y40ox1dSpfCUrizXx
+kb4p58Xml0PsB8dg3b4RDUgKwGC37ne5xmDnigyJPbiB2XJ6Xc46oPCjh86XROTK
+wEBB2lY67ClBlSlvC2V9KmbTboRQkLdQDhOaUosMb99zRb0EWqDLaFkZVjY5HI7i
+0pTveE6dI12NfHhTwKjZ5pUiAZQGlKA6J1dMjY2unxHZkQj5MlMfrLSyJHZxccdJ
+xD94T6OTcTHt/XmMpI2AObpewZDdChDQmcYDZXGfAhFoJmbvXsmLMGXKgzKoZ/ls
+RmLsQhh7+/r8E+Pn5r+A6Hh4uAc14ApyEP0ckKeIXw1C6pepHM4E8TEXVr/IA6K/
+z6jlHORixIFX7iNOnfHh+qwOgZw40D6JnBfEzjFi+T2Cy+JzN2uy7I8UnecTMGo3
+5t6astPy6xcH6kZYzFTV7XERR6LIIVyLAiMFd8kF5MbJ8N5ElRFsFHPW+82N2HDX
+c60iSaTB85k6R6xd8JIKDiaKE4sSuw2wHFCKq33d/GamYezp1wO+bVUQg88efljC
+2JNFyD+vl30josqhw1HcmbE1TP3DlYeIL5jQOlxCMsgai6JtTfHFM/5MYwARAQAB
+tBNzZWN1cml0eUByYW1zZXkuZGV2iQJUBBMBCAA+FiEE4drPD+/ofZ570fAYq0bv
+vXQCywIFAl+Z9gECGwMFCQeGH4AFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQ
+q0bvvXQCywIkEA//Qcwv8MtTCy01LHZd9c7VslwhNdXQDYymcTyjcYw8x7O22m4B
+3hXE6vqAplFhVxxkqXB2ef0tQuzxhPHNJgkCE4Wq4i+V6qGpaSVHQT2W6DN/NIhL
+vS8OdScc6zddmIbIkSrzVVAtjwehFNEIrX3DnbbbK+Iku7vsKT5EclOluIsjlYoX
+goW8IeReyDBqOe2H3hoCGw6EA0D/NYV2bJnfy53rXVIyarsXXeOLp7eNEH6Td7aW
+PVSrMZJe1t+knrEGnEdrXWzlg4lCJJCtemGv+pKBUomnyISXSdqyoRCCzvQjqyig
+2kRebUX8BXPW33p4OXPj9sIboUOjZwormWwqqbFMO+J4TiVCUoEoheI7emPFRcNN
+QtPJrjbY1++OznBc0GRpfeUkGoU1cbRl1bnepnFIZMTDLkrVW6I1Y4q8ZVwX3BkE
+N81ctFrRpHBlU36EdHvjPQmGtuiL77Qq3fWmMv7yTvK1wHJAXfEb0ZJWHZCbck3w
+l0CVq0Z+UUAOM8Rp1N0N8m92xtapav0qCFU9qzf2J5qX6GRmWv+d29wPgFHzDWBm
+nnrYYIA4wJLx00U6SMcVBSnNe91B+RfGY5XQhbWPjQQecOGCSDsxaFAq2MeOVJyZ
+bIjLYfG9GxoLKr5R7oLRJvZI4nKKBc1Kci/crZbdiSdQhSQGlDz88F1OHeCIdQQQ
+EQgAHRYhBOhdAxHd+lus86YQ57Atl5icjAcbBQJfmfdIAAoJELAtl5icjAcbFVcA
+/1LqB3ZjsnXDAvvAXZVjSPqofSlpMLeRQP6IM/A9Odq0AQCZrtZc1knOMGEcjppK
+Rk+sy/R0Mshy8TDuaZIRgh2Ux7kCDQRfmfYBARAAmchKzzVz7IaEq7PnZDb3szQs
+T/+E9F3m39yOpV4fEB1YzObonFakXNT7Gw2tZEx0eitUMqQ/13jjfu3UdzlKl2bR
+qA8LrSQRhB+PTC9A1XvwxCUYhhjGiLzJ9CZL6hBQB43qHOmE9XJPme90geLsF+gK
+u39Waj1SNWzwGg+Gy1Gl5f2AJoDTxznreCuFGj+Vfaczt/hlfgqpOdb9jsmdoE7t
+3DSWppA9dRHWwQSgE6J28rR4QySBcqyXS6IMykqaJn7Z26yNIaITLnHCZOSY8zhP
+ha7GFsN549EOCgECbrnPt9dmI2+hQE0RO0e7SOBNsIf5sz/i7urhwuj0CbOqhjc2
+X1AEVNFCVcb6HPi/AWefdFCRu0gaWQxn5g+9nkq5slEgvzCCiKYzaBIcr8qR6Hb4
+FaOPVPxO8vndRouq57Ws8XpAwbPttioFuCqF4u9K+tK/8e2/R8QgRYJsE3Cz/Fu8
++pZFpMnqbDEbK3DL3ss+1ed1sky+mDV8qXXeI33XW5hMFnk1JWshUjHNlQmE6ftC
+U0xSTMVUtwJhzH2zDp8lEdu7qi3EsNULOl68ozDr6soWAvCbHPeTdTOnFySGCleG
+/3TonsoZJs/sSPPJnxFQ1DtgQL6EbhIwa0ZwU4eKYVHZ9tjxuMX3teFzRvOrJjgs
++ywGlsIURtEckT5Y6nMAEQEAAYkCPAQYAQgAJhYhBOHazw/v6H2ee9HwGKtG7710
+AssCBQJfmfYBAhsMBQkHhh+AAAoJEKtG7710AssC8NcP/iDAcy1aZFvkA0EbZ85p
+i7/+ywtE/1wF4U4/9OuLcoskqGGnl1pJNPooMOSBCfreoTB8HimT0Fln0CoaOm4Q
+pScNq39JXmf4VxauqUJVARByP6zUfgYarqoaZNeuFF0S4AZJ2HhGzaQPjDz1uKVM
+PE6tQSgQkFzdZ9AtRA4vElTH6yRAgmepUsOihk0b0gUtVnwtRYZ8e0Qt3ie97a73
+DxLgAgedFRUbLRYiT0vNaYbainBsLWKpN/T8odwIg/smP0Khjp/ckV60cZTdBiPR
+szBTPJESMUTu0VPntc4gWwGsmhZJg/Tt/qP08XYo3VxNYBegyuWwNR66zDWvwvGH
+muMv5UchuDxp6Rt3JkIO4voMT1JSjWy9p8krkPEE4V6PxAagLjdZSkt92wVLiK5x
+y5gNrtPhU45YdRAKHr36OvJBJQ42CDaZ6nzrzghcIp9CZ7ANHrI+QLRM/csz+AGA
+szSp6S4mc1lnxxfbOhPPpebZPn0nIAXoZnnoVKdrxBVedPQHT59ZFvKTQ9Fs7gd3
+sYNuc7tJGFGC2CxBH4ANDpOQkc5q9JJ1HSGrXU3juxIiRgfA26Q22S9c71dXjElw
+Ri584QH+bL6kkYmm8xpKF6TVwhwu5xx/jBPrbWqFrtbvLNrnfPoapTihBfdIhkT6
+nmgawbBHA02D5xEqB5SU3WJu
+=eJNx
+-----END PGP PUBLIC KEY BLOCK-----
+```
diff --git a/lib/collection/composer.json b/lib/collection/composer.json
index 9e443d93..98862ee4 100644
--- a/lib/collection/composer.json
+++ b/lib/collection/composer.json
@@ -1,7 +1,7 @@
{
"name": "ramsey/collection",
"type": "library",
- "description": "A PHP 7.2+ library for representing and manipulating collections.",
+ "description": "A PHP library for representing and manipulating collections.",
"keywords": [
"array",
"collection",
@@ -19,25 +19,27 @@
}
],
"require": {
- "php": "^7.2 || ^8"
+ "php": "^7.3 || ^8",
+ "symfony/polyfill-php81": "^1.23"
},
"require-dev": {
"captainhook/captainhook": "^5.3",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"ergebnis/composer-normalize": "^2.6",
- "fzaninotto/faker": "^1.5",
+ "fakerphp/faker": "^1.5",
"hamcrest/hamcrest-php": "^2",
- "jangregor/phpstan-prophecy": "^0.6",
+ "jangregor/phpstan-prophecy": "^0.8",
"mockery/mockery": "^1.3",
+ "phpspec/prophecy-phpunit": "^2.0",
"phpstan/extension-installer": "^1",
"phpstan/phpstan": "^0.12.32",
"phpstan/phpstan-mockery": "^0.12.5",
"phpstan/phpstan-phpunit": "^0.12.11",
- "phpunit/phpunit": "^8.5",
+ "phpunit/phpunit": "^8.5 || ^9",
"psy/psysh": "^0.10.4",
"slevomat/coding-standard": "^6.3",
"squizlabs/php_codesniffer": "^3.5",
- "vimeo/psalm": "^3.12.2"
+ "vimeo/psalm": "^4.4"
},
"config": {
"sort-packages": true
@@ -50,7 +52,8 @@
"autoload-dev": {
"psr-4": {
"Ramsey\\Console\\": "resources/console/",
- "Ramsey\\Collection\\Test\\": "tests/"
+ "Ramsey\\Collection\\Test\\": "tests/",
+ "Ramsey\\Test\\Generics\\": "tests/generics/"
},
"files": [
"vendor/hamcrest/hamcrest-php/hamcrest/Hamcrest.php"
@@ -58,48 +61,42 @@
},
"scripts": {
"post-autoload-dump": "captainhook install --ansi -f -s",
- "br:analyze": [
- "@br:analyze:phpstan",
- "@br:analyze:psalm"
+ "dev:analyze": [
+ "@dev:analyze:phpstan",
+ "@dev:analyze:psalm"
],
- "br:analyze:phpstan": "phpstan --memory-limit=1G analyse",
- "br:analyze:psalm": "psalm --diff --diff-methods --config=psalm.xml",
- "br:build:clean": "git clean -fX build/.",
- "br:build:clear-cache": "git clean -fX build/cache/.",
- "br:lint": "phpcs --cache=build/cache/phpcs.cache",
- "br:lint:fix": "./bin/lint-fix.sh",
- "br:repl": [
+ "dev:analyze:phpstan": "phpstan --memory-limit=1G analyse",
+ "dev:analyze:psalm": "psalm --diff --config=psalm.xml",
+ "dev:build:clean": "git clean -fX build/.",
+ "dev:build:clear-cache": "git clean -fX build/cache/.",
+ "dev:lint": "phpcs --cache=build/cache/phpcs.cache",
+ "dev:lint:fix": "./bin/lint-fix.sh",
+ "dev:repl": [
"echo ; echo 'Type ./bin/repl to start the REPL.'"
],
- "br:test": "phpunit",
- "br:test:all": [
- "@br:lint",
- "@br:analyze",
- "@br:test"
+ "dev:test": "phpunit",
+ "dev:test:all": [
+ "@dev:lint",
+ "@dev:analyze",
+ "@dev:test"
],
- "br:test:coverage:ci": "phpunit --coverage-clover build/logs/clover.xml",
- "br:test:coverage:html": "phpunit --coverage-html build/coverage",
- "pre-commit": [
- "@br:lint:fix",
- "@br:lint",
- "@br:analyze"
- ],
- "test": "@br:test:all"
+ "dev:test:coverage:ci": "phpunit --coverage-clover build/logs/clover.xml",
+ "dev:test:coverage:html": "phpunit --coverage-html build/coverage",
+ "test": "@dev:test:all"
},
"scripts-descriptions": {
- "br:analyze": "Performs static analysis on the code base.",
- "br:analyze:phpstan": "Runs the PHPStan static analyzer.",
- "br:analyze:psalm": "Runs the Psalm static analyzer.",
- "br:build:clean": "Removes everything not under version control from the build directory.",
- "br:build:clear-cache": "Removes everything not under version control from build/cache/.",
- "br:lint": "Checks all source code for coding standards issues.",
- "br:lint:fix": "Checks source code for coding standards issues and fixes them, if possible.",
- "br:repl": "Note: Use ./bin/repl to run the REPL.",
- "br:test": "Runs the full unit test suite.",
- "br:test:all": "Runs linting, static analysis, and unit tests.",
- "br:test:coverage:ci": "Runs the unit test suite and generates a Clover coverage report.",
- "br:test:coverage:html": "Runs the unit tests suite and generates an HTML coverage report.",
- "pre-commit": "These commands are run as part of a Git pre-commit hook installed using captainhook/captainhook. Each command should be prepared to accept a list of space-separated staged files.",
+ "dev:analyze": "Performs static analysis on the code base.",
+ "dev:analyze:phpstan": "Runs the PHPStan static analyzer.",
+ "dev:analyze:psalm": "Runs the Psalm static analyzer.",
+ "dev:build:clean": "Removes everything not under version control from the build directory.",
+ "dev:build:clear-cache": "Removes everything not under version control from build/cache/.",
+ "dev:lint": "Checks all source code for coding standards issues.",
+ "dev:lint:fix": "Checks source code for coding standards issues and fixes them, if possible.",
+ "dev:repl": "Note: Use ./bin/repl to run the REPL.",
+ "dev:test": "Runs the full unit test suite.",
+ "dev:test:all": "Runs linting, static analysis, and unit tests.",
+ "dev:test:coverage:ci": "Runs the unit test suite and generates a Clover coverage report.",
+ "dev:test:coverage:html": "Runs the unit tests suite and generates an HTML coverage report.",
"test": "Shortcut to run the full test suite."
}
}
diff --git a/lib/collection/src/AbstractArray.php b/lib/collection/src/AbstractArray.php
index f8b4be2c..d72dbe69 100644
--- a/lib/collection/src/AbstractArray.php
+++ b/lib/collection/src/AbstractArray.php
@@ -23,20 +23,23 @@
/**
* This class provides a basic implementation of `ArrayInterface`, to minimize
* the effort required to implement this interface.
+ *
+ * @template T
+ * @implements ArrayInterface
*/
abstract class AbstractArray implements ArrayInterface
{
/**
* The items of this array.
*
- * @var mixed[]
+ * @var array
*/
protected $data = [];
/**
* Constructs a new array object.
*
- * @param mixed[] $data The initial items to add to this array.
+ * @param array $data The initial items to add to this array.
*/
public function __construct(array $data = [])
{
@@ -52,7 +55,7 @@ public function __construct(array $data = [])
*
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php IteratorAggregate::getIterator()
*
- * @return ArrayIterator
+ * @return Traversable
*/
public function getIterator(): Traversable
{
@@ -64,7 +67,7 @@ public function getIterator(): Traversable
*
* @link http://php.net/manual/en/arrayaccess.offsetexists.php ArrayAccess::offsetExists()
*
- * @param mixed $offset The offset to check.
+ * @param array-key $offset The offset to check.
*/
public function offsetExists($offset): bool
{
@@ -76,11 +79,14 @@ public function offsetExists($offset): bool
*
* @link http://php.net/manual/en/arrayaccess.offsetget.php ArrayAccess::offsetGet()
*
- * @param mixed $offset The offset for which a value should be returned.
+ * @param array-key $offset The offset for which a value should be returned.
*
- * @return mixed|null the value stored at the offset, or null if the offset
+ * @return T|null the value stored at the offset, or null if the offset
* does not exist.
+ *
+ * @psalm-suppress InvalidAttribute
*/
+ #[\ReturnTypeWillChange] // phpcs:ignore
public function offsetGet($offset)
{
return $this->data[$offset] ?? null;
@@ -91,10 +97,11 @@ public function offsetGet($offset)
*
* @link http://php.net/manual/en/arrayaccess.offsetset.php ArrayAccess::offsetSet()
*
- * @param mixed|null $offset The offset to set. If `null`, the value may be
+ * @param array-key|null $offset The offset to set. If `null`, the value may be
* set at a numerically-indexed offset.
- * @param mixed $value The value to set at the given offset.
+ * @param T $value The value to set at the given offset.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function offsetSet($offset, $value): void
{
if ($offset === null) {
@@ -109,7 +116,7 @@ public function offsetSet($offset, $value): void
*
* @link http://php.net/manual/en/arrayaccess.offsetunset.php ArrayAccess::offsetUnset()
*
- * @param mixed $offset The offset to remove from the array.
+ * @param array-key $offset The offset to remove from the array.
*/
public function offsetUnset($offset): void
{
@@ -119,6 +126,8 @@ public function offsetUnset($offset): void
/**
* Returns a serialized string representation of this array object.
*
+ * @deprecated The Serializable interface will go away in PHP 9.
+ *
* @link http://php.net/manual/en/serializable.serialize.php Serializable::serialize()
*
* @return string a PHP serialized string.
@@ -128,9 +137,24 @@ public function serialize(): string
return serialize($this->data);
}
+ /**
+ * Returns data suitable for PHP serialization.
+ *
+ * @link https://www.php.net/manual/en/language.oop5.magic.php#language.oop5.magic.serialize
+ * @link https://www.php.net/serialize
+ *
+ * @return array
+ */
+ public function __serialize(): array
+ {
+ return $this->data;
+ }
+
/**
* Converts a serialized string representation into an instance object.
*
+ * @deprecated The Serializable interface will go away in PHP 9.
+ *
* @link http://php.net/manual/en/serializable.unserialize.php Serializable::unserialize()
*
* @param string $serialized A PHP serialized string to unserialize.
@@ -139,7 +163,20 @@ public function serialize(): string
*/
public function unserialize($serialized): void
{
- $this->data = unserialize($serialized, ['allowed_classes' => false]);
+ /** @var array $data */
+ $data = unserialize($serialized, ['allowed_classes' => false]);
+
+ $this->data = $data;
+ }
+
+ /**
+ * Adds unserialized data to the object.
+ *
+ * @param array $data
+ */
+ public function __unserialize(array $data): void
+ {
+ $this->data = $data;
}
/**
@@ -152,27 +189,19 @@ public function count(): int
return count($this->data);
}
- /**
- * Removes all items from this array.
- */
public function clear(): void
{
$this->data = [];
}
/**
- * Returns a native PHP array representation of this array object.
- *
- * @return mixed[]
+ * @inheritDoc
*/
public function toArray(): array
{
return $this->data;
}
- /**
- * Returns `true` if this array is empty.
- */
public function isEmpty(): bool
{
return count($this->data) === 0;
diff --git a/lib/collection/src/AbstractCollection.php b/lib/collection/src/AbstractCollection.php
index 84e8b882..d2cd1151 100644
--- a/lib/collection/src/AbstractCollection.php
+++ b/lib/collection/src/AbstractCollection.php
@@ -14,11 +14,11 @@
namespace Ramsey\Collection;
+use Closure;
use Ramsey\Collection\Exception\CollectionMismatchException;
use Ramsey\Collection\Exception\InvalidArgumentException;
use Ramsey\Collection\Exception\InvalidSortOrderException;
use Ramsey\Collection\Exception\OutOfBoundsException;
-use Ramsey\Collection\Exception\ValueExtractionException;
use Ramsey\Collection\Tool\TypeTrait;
use Ramsey\Collection\Tool\ValueExtractorTrait;
use Ramsey\Collection\Tool\ValueToStringTrait;
@@ -32,6 +32,7 @@
use function current;
use function end;
use function in_array;
+use function is_int;
use function reset;
use function sprintf;
use function unserialize;
@@ -40,6 +41,10 @@
/**
* This class provides a basic implementation of `CollectionInterface`, to
* minimize the effort required to implement this interface
+ *
+ * @template T
+ * @extends AbstractArray
+ * @implements CollectionInterface
*/
abstract class AbstractCollection extends AbstractArray implements CollectionInterface
{
@@ -48,14 +53,7 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
use ValueExtractorTrait;
/**
- * Ensures that this collection contains the specified element.
- *
- * @param mixed $element The element to add to the collection.
- *
- * @return bool `true` if this collection changed as a result of the call.
- *
- * @throws InvalidArgumentException when the element does not match the
- * specified type for this collection.
+ * @inheritDoc
*/
public function add($element): bool
{
@@ -65,10 +63,7 @@ public function add($element): bool
}
/**
- * Returns `true` if this collection contains the specified element.
- *
- * @param mixed $element The element to check whether the collection contains.
- * @param bool $strict Whether to perform a strict type check on the value.
+ * @inheritDoc
*/
public function contains($element, bool $strict = true): bool
{
@@ -76,14 +71,7 @@ public function contains($element, bool $strict = true): bool
}
/**
- * Sets the given value to the given offset in the array.
- *
- * @param mixed|null $offset The position to set the value in the array, or
- * `null` to append the value to the array.
- * @param mixed $value The value to set at the given offset.
- *
- * @throws InvalidArgumentException when the value does not match the
- * specified type for this collection.
+ * @inheritDoc
*/
public function offsetSet($offset, $value): void
{
@@ -102,12 +90,7 @@ public function offsetSet($offset, $value): void
}
/**
- * Removes a single instance of the specified element from this collection,
- * if it is present.
- *
- * @param mixed $element The element to remove from the collection.
- *
- * @return bool `true` if an element was removed as a result of this call.
+ * @inheritDoc
*/
public function remove($element): bool
{
@@ -121,31 +104,25 @@ public function remove($element): bool
}
/**
- * Returns the values from given property or method.
- *
- * @param string $propertyOrMethod The property or method name to filter by.
- *
- * @return mixed[]
- *
- * @throws ValueExtractionException if property or method is not defined.
+ * @inheritDoc
*/
public function column(string $propertyOrMethod): array
{
$temp = [];
foreach ($this->data as $item) {
- $temp[] = $this->extractValue($item, $propertyOrMethod);
+ /** @var mixed $value */
+ $value = $this->extractValue($item, $propertyOrMethod);
+
+ /** @psalm-suppress MixedAssignment */
+ $temp[] = $value;
}
return $temp;
}
/**
- * Returns the first item of the collection.
- *
- * @return mixed
- *
- * @throws OutOfBoundsException when the collection is empty.
+ * @inheritDoc
*/
public function first()
{
@@ -155,15 +132,14 @@ public function first()
reset($this->data);
- return current($this->data);
+ /** @var T $first */
+ $first = current($this->data);
+
+ return $first;
}
/**
- * Returns the last item of the collection.
- *
- * @return mixed
- *
- * @throws OutOfBoundsException when the collection is empty.
+ * @inheritDoc
*/
public function last()
{
@@ -171,27 +147,13 @@ public function last()
throw new OutOfBoundsException('Can\'t determine last item. Collection is empty');
}
+ /** @var T $item */
$item = end($this->data);
reset($this->data);
return $item;
}
- /**
- * Returns a sorted collection.
- *
- * {@inheritdoc}
- *
- * @param string $propertyOrMethod The property or method to sort by.
- * @param string $order The sort order for the resulting collection (one of
- * this interface's `SORT_*` constants).
- *
- * @return CollectionInterface
- *
- * @throws InvalidSortOrderException if neither "asc" nor "desc" was given
- * as the order.
- * @throws ValueExtractionException if property or method is not defined.
- */
public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): CollectionInterface
{
if (!in_array($order, [self::SORT_ASC, self::SORT_DESC], true)) {
@@ -200,25 +162,26 @@ public function sort(string $propertyOrMethod, string $order = self::SORT_ASC):
$collection = clone $this;
- usort($collection->data, function ($a, $b) use ($propertyOrMethod, $order) {
- $aValue = $this->extractValue($a, $propertyOrMethod);
- $bValue = $this->extractValue($b, $propertyOrMethod);
+ usort(
+ $collection->data,
+ /**
+ * @param T $a
+ * @param T $b
+ */
+ function ($a, $b) use ($propertyOrMethod, $order): int {
+ /** @var mixed $aValue */
+ $aValue = $this->extractValue($a, $propertyOrMethod);
- return ($aValue <=> $bValue) * ($order === self::SORT_DESC ? -1 : 1);
- });
+ /** @var mixed $bValue */
+ $bValue = $this->extractValue($b, $propertyOrMethod);
+
+ return ($aValue <=> $bValue) * ($order === self::SORT_DESC ? -1 : 1);
+ }
+ );
return $collection;
}
- /**
- * Returns a filtered collection.
- *
- * {@inheritdoc}
- *
- * @param callable $callback A callable to use for filtering elements.
- *
- * @return CollectionInterface
- */
public function filter(callable $callback): CollectionInterface
{
$collection = clone $this;
@@ -228,84 +191,31 @@ public function filter(callable $callback): CollectionInterface
}
/**
- * Returns a collection of matching items.
- *
* {@inheritdoc}
- *
- * @param string $propertyOrMethod The property or method to evaluate.
- * @param mixed $value The value to match.
- *
- * @return CollectionInterface
- *
- * @throws ValueExtractionException if property or method is not defined.
*/
public function where(string $propertyOrMethod, $value): CollectionInterface
{
return $this->filter(function ($item) use ($propertyOrMethod, $value) {
+ /** @var mixed $accessorValue */
$accessorValue = $this->extractValue($item, $propertyOrMethod);
return $accessorValue === $value;
});
}
- /**
- * Applies a callback to each item of the collection.
- *
- * {@inheritdoc}
- *
- * @param callable $callback A callable to apply to each item of the
- * collection.
- *
- * @return CollectionInterface
- */
public function map(callable $callback): CollectionInterface
{
- $collection = clone $this;
- array_map($callback, $collection->data);
-
- return $collection;
+ return new Collection('mixed', array_map($callback, $this->data));
}
- /**
- * Create a new collection with divergent items between current and given
- * collection.
- *
- * @param CollectionInterface $other The collection to check for divergent
- * items.
- *
- * @return CollectionInterface
- *
- * @throws CollectionMismatchException if the given collection is not of the
- * same type.
- */
public function diff(CollectionInterface $other): CollectionInterface
{
- if (!$other instanceof static) {
- throw new CollectionMismatchException('Collection must be of type ' . static::class);
- }
+ $this->compareCollectionTypes($other);
- // When using generics (Collection.php, Set.php, etc),
- // we also need to make sure that the internal types match each other
- if ($other->getType() !== $this->getType()) {
- throw new CollectionMismatchException('Collection items must be of type ' . $this->getType());
- }
+ $diffAtoB = array_udiff($this->data, $other->toArray(), $this->getComparator());
+ $diffBtoA = array_udiff($other->toArray(), $this->data, $this->getComparator());
- $comparator = function ($a, $b): int {
- // If the two values are object, we convert them to unique scalars.
- // If the collection contains mixed values (unlikely) where some are objects
- // and some are not, we leave them as they are.
- // The comparator should still work and the result of $a < $b should
- // be consistent but unpredictable since not documented.
- if (is_object($a) && is_object($b)) {
- $a = spl_object_id($a);
- $b = spl_object_id($b);
- }
-
- return $a === $b ? 0 : ($a < $b ? 1 : -1);
- };
-
- $diffAtoB = array_udiff($this->data, $other->data, $comparator);
- $diffBtoA = array_udiff($other->data, $this->data, $comparator);
+ /** @var array $diff */
$diff = array_merge($diffAtoB, $diffBtoA);
$collection = clone $this;
@@ -314,45 +224,12 @@ public function diff(CollectionInterface $other): CollectionInterface
return $collection;
}
- /**
- * Create a new collection with intersecting item between current and given
- * collection.
- *
- * @param CollectionInterface $other The collection to check for
- * intersecting items.
- *
- * @return CollectionInterface
- *
- * @throws CollectionMismatchException if the given collection is not of the
- * same type.
- */
public function intersect(CollectionInterface $other): CollectionInterface
{
- if (!$other instanceof static) {
- throw new CollectionMismatchException('Collection must be of type ' . static::class);
- }
+ $this->compareCollectionTypes($other);
- // When using generics (Collection.php, Set.php, etc),
- // we also need to make sure that the internal types match each other
- if ($other->getType() !== $this->getType()) {
- throw new CollectionMismatchException('Collection items must be of type ' . $this->getType());
- }
-
- $comparator = function ($a, $b): int {
- // If the two values are object, we convert them to unique scalars.
- // If the collection contains mixed values (unlikely) where some are objects
- // and some are not, we leave them as they are.
- // The comparator should still work and the result of $a < $b should
- // be consistent but unpredictable since not documented.
- if (is_object($a) && is_object($b)) {
- $a = spl_object_id($a);
- $b = spl_object_id($b);
- }
-
- return $a === $b ? 0 : ($a < $b ? 1 : -1);
- };
-
- $intersect = array_uintersect($this->data, $other->data, $comparator);
+ /** @var array $intersect */
+ $intersect = array_uintersect($this->data, $other->toArray(), $this->getComparator());
$collection = clone $this;
$collection->data = $intersect;
@@ -360,18 +237,9 @@ public function intersect(CollectionInterface $other): CollectionInterface
return $collection;
}
- /**
- * Merge current items and items of given collections into a new one.
- *
- * @param CollectionInterface ...$collections The collections to merge.
- *
- * @return CollectionInterface
- *
- * @throws CollectionMismatchException if any of the given collections are not of the same type.
- */
public function merge(CollectionInterface ...$collections): CollectionInterface
{
- $temp = [$this->data];
+ $mergedCollection = clone $this;
foreach ($collections as $index => $collection) {
if (!$collection instanceof static) {
@@ -388,15 +256,16 @@ public function merge(CollectionInterface ...$collections): CollectionInterface
);
}
- $temp[] = $collection->toArray();
+ foreach ($collection as $key => $value) {
+ if (is_int($key)) {
+ $mergedCollection[] = $value;
+ } else {
+ $mergedCollection[$key] = $value;
+ }
+ }
}
- $merge = array_merge(...$temp);
-
- $collection = clone $this;
- $collection->data = $merge;
-
- return $collection;
+ return $mergedCollection;
}
/**
@@ -404,6 +273,46 @@ public function merge(CollectionInterface ...$collections): CollectionInterface
*/
public function unserialize($serialized): void
{
- $this->data = unserialize($serialized, ['allowed_classes' => [$this->getType()]]);
+ /** @var array $data */
+ $data = unserialize($serialized, ['allowed_classes' => [$this->getType()]]);
+
+ $this->data = $data;
+ }
+
+ /**
+ * @param CollectionInterface $other
+ */
+ private function compareCollectionTypes(CollectionInterface $other): void
+ {
+ if (!$other instanceof static) {
+ throw new CollectionMismatchException('Collection must be of type ' . static::class);
+ }
+
+ // When using generics (Collection.php, Set.php, etc),
+ // we also need to make sure that the internal types match each other
+ if ($other->getType() !== $this->getType()) {
+ throw new CollectionMismatchException('Collection items must be of type ' . $this->getType());
+ }
+ }
+
+ private function getComparator(): Closure
+ {
+ return /**
+ * @param T $a
+ * @param T $b
+ */
+ function ($a, $b): int {
+ // If the two values are object, we convert them to unique scalars.
+ // If the collection contains mixed values (unlikely) where some are objects
+ // and some are not, we leave them as they are.
+ // The comparator should still work and the result of $a < $b should
+ // be consistent but unpredictable since not documented.
+ if (is_object($a) && is_object($b)) {
+ $a = spl_object_id($a);
+ $b = spl_object_id($b);
+ }
+
+ return $a === $b ? 0 : ($a < $b ? 1 : -1);
+ };
}
}
diff --git a/lib/collection/src/AbstractSet.php b/lib/collection/src/AbstractSet.php
index 674fda03..1126ccb0 100644
--- a/lib/collection/src/AbstractSet.php
+++ b/lib/collection/src/AbstractSet.php
@@ -14,25 +14,18 @@
namespace Ramsey\Collection;
-use Ramsey\Collection\Exception\InvalidArgumentException;
-
/**
* This class contains the basic implementation of a collection that does not
* allow duplicated values (a set), to minimize the effort required to implement
* this specific type of collection.
+ *
+ * @template T
+ * @extends AbstractCollection
*/
abstract class AbstractSet extends AbstractCollection
{
/**
- * Adds the specified element to this set, if it is not already present.
- *
- * @param mixed $element The element to add to the set.
- *
- * @return bool `true` if this set did not already contain the specified
- * element.
- *
- * @throws InvalidArgumentException when the element does not match the
- * specified type for this set.
+ * @inheritDoc
*/
public function add($element): bool
{
@@ -44,14 +37,7 @@ public function add($element): bool
}
/**
- * Sets the given value to the given offset in this set, if it is not
- * already present.
- *
- * @param mixed|null $offset The offset is ignored and is treated as `null`.
- * @param mixed $value The value to set at the given offset.
- *
- * @throws InvalidArgumentException when the value does not match the
- * specified type for this set.
+ * @inheritDoc
*/
public function offsetSet($offset, $value): void
{
diff --git a/lib/collection/src/ArrayInterface.php b/lib/collection/src/ArrayInterface.php
index 81835cc8..27af6102 100644
--- a/lib/collection/src/ArrayInterface.php
+++ b/lib/collection/src/ArrayInterface.php
@@ -21,6 +21,10 @@
/**
* `ArrayInterface` provides traversable array functionality to data types.
+ *
+ * @template T
+ * @extends ArrayAccess
+ * @extends IteratorAggregate
*/
interface ArrayInterface extends
ArrayAccess,
@@ -36,7 +40,7 @@ public function clear(): void;
/**
* Returns a native PHP array representation of this array object.
*
- * @return mixed[]
+ * @return array
*/
public function toArray(): array;
diff --git a/lib/collection/src/Collection.php b/lib/collection/src/Collection.php
index e4db68df..1299c12c 100644
--- a/lib/collection/src/Collection.php
+++ b/lib/collection/src/Collection.php
@@ -69,6 +69,9 @@
* // the collection is a collection of My\Foo objects
* }
* ```
+ *
+ * @template T
+ * @extends AbstractCollection
*/
class Collection extends AbstractCollection
{
@@ -88,7 +91,7 @@ class Collection extends AbstractCollection
*
* @param string $collectionType The type (FQCN) associated with this
* collection.
- * @param mixed[] $data The initial items to store in the collection.
+ * @param array $data The initial items to store in the collection.
*/
public function __construct(string $collectionType, array $data = [])
{
@@ -96,9 +99,6 @@ public function __construct(string $collectionType, array $data = [])
parent::__construct($data);
}
- /**
- * Returns the type associated with this collection.
- */
public function getType(): string
{
return $this->collectionType;
diff --git a/lib/collection/src/CollectionInterface.php b/lib/collection/src/CollectionInterface.php
index c865fa9f..aa86feb0 100644
--- a/lib/collection/src/CollectionInterface.php
+++ b/lib/collection/src/CollectionInterface.php
@@ -19,6 +19,9 @@
*
* Some collections allow duplicate elements and others do not. Some are ordered
* and others unordered.
+ *
+ * @template T
+ * @extends ArrayInterface
*/
interface CollectionInterface extends ArrayInterface
{
@@ -52,18 +55,20 @@ interface CollectionInterface extends ArrayInterface
* (rather than returning `false`). This preserves the invariant that a
* collection always contains the specified element after this call returns.
*
- * @param mixed $element The element to add to the collection.
+ * @param T $element The element to add to the collection.
*
* @return bool `true` if this collection changed as a result of the call.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function add($element): bool;
/**
* Returns `true` if this collection contains the specified element.
*
- * @param mixed $element The element to check whether the collection contains.
+ * @param T $element The element to check whether the collection contains.
* @param bool $strict Whether to perform a strict type check on the value.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function contains($element, bool $strict = true): bool;
/**
@@ -75,10 +80,11 @@ public function getType(): string;
* Removes a single instance of the specified element from this collection,
* if it is present.
*
- * @param mixed $element The element to remove from the collection.
+ * @param T $element The element to remove from the collection.
*
* @return bool `true` if an element was removed as a result of this call.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function remove($element): bool;
/**
@@ -86,21 +92,21 @@ public function remove($element): bool;
*
* @param string $propertyOrMethod The property or method name to filter by.
*
- * @return mixed[]
+ * @return list
*/
public function column(string $propertyOrMethod): array;
/**
* Returns the first item of the collection.
*
- * @return mixed
+ * @return T
*/
public function first();
/**
* Returns the last item of the collection.
*
- * @return mixed
+ * @return T
*/
public function last();
@@ -114,7 +120,7 @@ public function last();
* @param string $order The sort order for the resulting collection (one of
* this interface's `SORT_*` constants).
*
- * @return CollectionInterface
+ * @return CollectionInterface
*/
public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): self;
@@ -128,9 +134,9 @@ public function sort(string $propertyOrMethod, string $order = self::SORT_ASC):
* See the {@link http://php.net/manual/en/function.array-filter.php PHP array_filter() documentation}
* for examples of how the `$callback` parameter works.
*
- * @param callable $callback A callable to use for filtering elements.
+ * @param callable(T):bool $callback A callable to use for filtering elements.
*
- * @return CollectionInterface
+ * @return CollectionInterface
*/
public function filter(callable $callback): self;
@@ -141,25 +147,28 @@ public function filter(callable $callback): self;
* a new one.
*
* @param string $propertyOrMethod The property or method to evaluate.
- * @param mixed $value The value to match.
+ * @param mixed $value The value to match.
*
- * @return CollectionInterface
+ * @return CollectionInterface
*/
public function where(string $propertyOrMethod, $value): self;
/**
* Apply a given callback method on each item of the collection.
*
- * This will always leave the original collection untouched and will return
- * a new one.
+ * This will always leave the original collection untouched. The new
+ * collection is created by mapping the callback to each item of the
+ * original collection.
*
* See the {@link http://php.net/manual/en/function.array-map.php PHP array_map() documentation}
* for examples of how the `$callback` parameter works.
*
- * @param callable $callback A callable to apply to each item of the
- * collection.
+ * @param callable(T):TCallbackReturn $callback A callable to apply to each
+ * item of the collection.
+ *
+ * @return CollectionInterface
*
- * @return CollectionInterface
+ * @template TCallbackReturn
*/
public function map(callable $callback): self;
@@ -167,10 +176,10 @@ public function map(callable $callback): self;
* Create a new collection with divergent items between current and given
* collection.
*
- * @param CollectionInterface $other The collection to check for divergent
+ * @param CollectionInterface $other The collection to check for divergent
* items.
*
- * @return CollectionInterface
+ * @return CollectionInterface
*/
public function diff(CollectionInterface $other): self;
@@ -178,19 +187,19 @@ public function diff(CollectionInterface $other): self;
* Create a new collection with intersecting item between current and given
* collection.
*
- * @param CollectionInterface $other The collection to check for
+ * @param CollectionInterface $other The collection to check for
* intersecting items.
*
- * @return CollectionInterface
+ * @return CollectionInterface
*/
public function intersect(CollectionInterface $other): self;
/**
* Merge current items and items of given collections into a new one.
*
- * @param CollectionInterface ...$collections The collections to merge.
+ * @param CollectionInterface ...$collections The collections to merge.
*
- * @return CollectionInterface
+ * @return CollectionInterface
*/
public function merge(CollectionInterface ...$collections): self;
}
diff --git a/lib/collection/src/DoubleEndedQueue.php b/lib/collection/src/DoubleEndedQueue.php
index 4eb4dbea..c9c59502 100644
--- a/lib/collection/src/DoubleEndedQueue.php
+++ b/lib/collection/src/DoubleEndedQueue.php
@@ -20,6 +20,10 @@
/**
* This class provides a basic implementation of `DoubleEndedQueueInterface`, to
* minimize the effort required to implement this interface.
+ *
+ * @template T
+ * @extends Queue
+ * @implements DoubleEndedQueueInterface
*/
class DoubleEndedQueue extends Queue implements DoubleEndedQueueInterface
{
@@ -31,19 +35,7 @@ class DoubleEndedQueue extends Queue implements DoubleEndedQueueInterface
private $tail = -1;
/**
- * Sets the given value to the given offset in the queue.
- *
- * Since arbitrary offsets may not be manipulated in a queue, this method
- * serves only to fulfill the `ArrayAccess` interface requirements. It is
- * invoked by other operations when adding values to the queue.
- *
- * @link http://php.net/manual/en/arrayaccess.offsetset.php ArrayAccess::offsetSet()
- *
- * @param mixed|null $offset The offset is ignored and is treated as `null`.
- * @param mixed $value The value to set at the given offset.
- *
- * @throws InvalidArgumentException when the value does not match the
- * specified type for this queue.
+ * @inheritDoc
*/
public function offsetSet($offset, $value): void
{
@@ -60,16 +52,7 @@ public function offsetSet($offset, $value): void
}
/**
- * Ensures that the specified element is inserted at the front of this queue.
- *
- * @see self::offerFirst()
- *
- * @param mixed $element The element to add to this queue.
- *
- * @return bool `true` if this queue changed as a result of the call.
- *
- * @throws InvalidArgumentException when the value does not match the
- * specified type for this queue.
+ * @inheritDoc
*/
public function addFirst($element): bool
{
@@ -88,16 +71,7 @@ public function addFirst($element): bool
}
/**
- * Ensures that the specified element in inserted at the end of this queue.
- *
- * @see Queue::add()
- *
- * @param mixed $element The element to add to this queue.
- *
- * @return bool `true` if this queue changed as a result of the call.
- *
- * @throws InvalidArgumentException when the value does not match the
- * specified type for this queue.
+ * @inheritDoc
*/
public function addLast($element): bool
{
@@ -105,13 +79,7 @@ public function addLast($element): bool
}
/**
- * Inserts the specified element at the front this queue.
- *
- * @see self::addFirst()
- *
- * @param mixed $element The element to add to this queue.
- *
- * @return bool `true` if the element was added to this queue, else `false`.
+ * @inheritDoc
*/
public function offerFirst($element): bool
{
@@ -123,14 +91,7 @@ public function offerFirst($element): bool
}
/**
- * Inserts the specified element at the end this queue.
- *
- * @see self::addLast()
- * @see Queue::offer()
- *
- * @param mixed $element The element to add to this queue.
- *
- * @return bool `true` if the element was added to this queue, else `false`.
+ * @inheritDoc
*/
public function offerLast($element): bool
{
@@ -138,17 +99,7 @@ public function offerLast($element): bool
}
/**
- * Retrieves and removes the head of this queue.
- *
- * This method differs from `pollFirst()` only in that it throws an
- * exception if this queue is empty.
- *
- * @see self::pollFirst()
- * @see Queue::remove()
- *
- * @return mixed the head of this queue.
- *
- * @throws NoSuchElementException if this queue is empty.
+ * @inheritDoc
*/
public function removeFirst()
{
@@ -156,38 +107,21 @@ public function removeFirst()
}
/**
- * Retrieves and removes the tail of this queue.
- *
- * This method differs from `pollLast()` only in that it throws an exception
- * if this queue is empty.
- *
- * @see self::pollLast()
- *
- * @return mixed the tail of this queue.
- *
- * @throws NoSuchElementException if this queue is empty.
+ * @inheritDoc
*/
public function removeLast()
{
- if ($this->count() === 0) {
+ $tail = $this->pollLast();
+
+ if ($tail === null) {
throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.');
}
- $tail = $this[$this->tail];
-
- unset($this[$this->tail]);
- $this->tail--;
-
return $tail;
}
/**
- * Retrieves and removes the head of this queue, or returns `null` if this
- * queue is empty.
- *
- * @see self::removeFirst()
- *
- * @return mixed|null the head of this queue, or `null` if this queue is empty.
+ * @inheritDoc
*/
public function pollFirst()
{
@@ -195,12 +129,7 @@ public function pollFirst()
}
/**
- * Retrieves and removes the tail of this queue, or returns `null` if this
- * queue is empty.
- *
- * @see self::removeLast()
- *
- * @return mixed|null the tail of this queue, or `null` if this queue is empty.
+ * @inheritDoc
*/
public function pollLast()
{
@@ -217,17 +146,7 @@ public function pollLast()
}
/**
- * Retrieves, but does not remove, the head of this queue.
- *
- * This method differs from `peekFirst()` only in that it throws an
- * exception if this queue is empty.
- *
- * @see self::peekFirst()
- * @see Queue::element()
- *
- * @return mixed the head of this queue.
- *
- * @throws NoSuchElementException if this queue is empty.
+ * @inheritDoc
*/
public function firstElement()
{
@@ -235,16 +154,7 @@ public function firstElement()
}
/**
- * Retrieves, but does not remove, the tail of this queue.
- *
- * This method differs from `peekLast()` only in that it throws an exception
- * if this queue is empty.
- *
- * @see self::peekLast()
- *
- * @return mixed the tail of this queue.
- *
- * @throws NoSuchElementException if this queue is empty.
+ * @inheritDoc
*/
public function lastElement()
{
@@ -256,13 +166,7 @@ public function lastElement()
}
/**
- * Retrieves, but does not remove, the head of this queue, or returns `null`
- * if this queue is empty.
- *
- * @see self::firstElement()
- * @see Queue::peek()
- *
- * @return mixed|null the head of this queue, or `null` if this queue is empty.
+ * @inheritDoc
*/
public function peekFirst()
{
@@ -270,12 +174,7 @@ public function peekFirst()
}
/**
- * Retrieves, but does not remove, the tail of this queue, or returns `null`
- * if this queue is empty.
- *
- * @see self::lastElement()
- *
- * @return mixed|null the tail of this queue, or `null` if this queue is empty
+ * @inheritDoc
*/
public function peekLast()
{
diff --git a/lib/collection/src/DoubleEndedQueueInterface.php b/lib/collection/src/DoubleEndedQueueInterface.php
index 6b23cf55..d7df5346 100644
--- a/lib/collection/src/DoubleEndedQueueInterface.php
+++ b/lib/collection/src/DoubleEndedQueueInterface.php
@@ -158,6 +158,9 @@
* ability to insert nulls. This is so because `null` is used as a special
* return value by various methods to indicated that the double-ended queue is
* empty.
+ *
+ * @template T
+ * @extends QueueInterface
*/
interface DoubleEndedQueueInterface extends QueueInterface
{
@@ -168,7 +171,7 @@ interface DoubleEndedQueueInterface extends QueueInterface
* When using a capacity-restricted double-ended queue, it is generally
* preferable to use the `offerFirst()` method.
*
- * @param mixed $element The element to add to the front of this queue.
+ * @param T $element The element to add to the front of this queue.
*
* @return bool `true` if this queue changed as a result of the call.
*
@@ -177,6 +180,7 @@ interface DoubleEndedQueueInterface extends QueueInterface
* Implementations should use a more-specific exception that extends
* `\RuntimeException`.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function addFirst($element): bool;
/**
@@ -188,7 +192,7 @@ public function addFirst($element): bool;
*
* This method is equivalent to `add()`.
*
- * @param mixed $element The element to add to the end of this queue.
+ * @param T $element The element to add to the end of this queue.
*
* @return bool `true` if this queue changed as a result of the call.
*
@@ -197,6 +201,7 @@ public function addFirst($element): bool;
* Implementations should use a more-specific exception that extends
* `\RuntimeException`.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function addLast($element): bool;
/**
@@ -207,10 +212,11 @@ public function addLast($element): bool;
* preferable to `addFirst()`, which can fail to insert an element only by
* throwing an exception.
*
- * @param mixed $element The element to add to the front of this queue.
+ * @param T $element The element to add to the front of this queue.
*
* @return bool `true` if the element was added to this queue, else `false`.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function offerFirst($element): bool;
/**
@@ -221,10 +227,11 @@ public function offerFirst($element): bool;
* preferable to `addLast()` which can fail to insert an element only by
* throwing an exception.
*
- * @param mixed $element The element to add to the end of this queue.
+ * @param T $element The element to add to the end of this queue.
*
* @return bool `true` if the element was added to this queue, else `false`.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function offerLast($element): bool;
/**
@@ -233,7 +240,7 @@ public function offerLast($element): bool;
* This method differs from `pollFirst()` only in that it throws an
* exception if this queue is empty.
*
- * @return mixed the first element in this queue.
+ * @return T the first element in this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
@@ -245,7 +252,7 @@ public function removeFirst();
* This method differs from `pollLast()` only in that it throws an exception
* if this queue is empty.
*
- * @return mixed the last element in this queue.
+ * @return T the last element in this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
@@ -255,7 +262,7 @@ public function removeLast();
* Retrieves and removes the head of this queue, or returns `null` if this
* queue is empty.
*
- * @return mixed|null the head of this queue, or `null` if this queue is empty.
+ * @return T|null the head of this queue, or `null` if this queue is empty.
*/
public function pollFirst();
@@ -263,7 +270,7 @@ public function pollFirst();
* Retrieves and removes the tail of this queue, or returns `null` if this
* queue is empty.
*
- * @return mixed|null the tail of this queue, or `null` if this queue is empty.
+ * @return T|null the tail of this queue, or `null` if this queue is empty.
*/
public function pollLast();
@@ -273,7 +280,7 @@ public function pollLast();
* This method differs from `peekFirst()` only in that it throws an
* exception if this queue is empty.
*
- * @return mixed the head of this queue.
+ * @return T the head of this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
@@ -285,7 +292,7 @@ public function firstElement();
* This method differs from `peekLast()` only in that it throws an exception
* if this queue is empty.
*
- * @return mixed the tail of this queue.
+ * @return T the tail of this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
@@ -295,7 +302,7 @@ public function lastElement();
* Retrieves, but does not remove, the head of this queue, or returns `null`
* if this queue is empty.
*
- * @return mixed|null the head of this queue, or `null` if this queue is empty.
+ * @return T|null the head of this queue, or `null` if this queue is empty.
*/
public function peekFirst();
@@ -303,7 +310,7 @@ public function peekFirst();
* Retrieves, but does not remove, the tail of this queue, or returns `null`
* if this queue is empty.
*
- * @return mixed|null the tail of this queue, or `null` if this queue is empty.
+ * @return T|null the tail of this queue, or `null` if this queue is empty.
*/
public function peekLast();
}
diff --git a/lib/collection/src/GenericArray.php b/lib/collection/src/GenericArray.php
index 2f9ab767..2b079aa5 100644
--- a/lib/collection/src/GenericArray.php
+++ b/lib/collection/src/GenericArray.php
@@ -16,6 +16,8 @@
/**
* `GenericArray` represents a standard array object.
+ *
+ * @extends AbstractArray
*/
class GenericArray extends AbstractArray
{
diff --git a/lib/collection/src/Map/AbstractMap.php b/lib/collection/src/Map/AbstractMap.php
index 6b2e97a0..ae9f2fe6 100644
--- a/lib/collection/src/Map/AbstractMap.php
+++ b/lib/collection/src/Map/AbstractMap.php
@@ -24,23 +24,22 @@
/**
* This class provides a basic implementation of `MapInterface`, to minimize the
* effort required to implement this interface.
+ *
+ * @template T
+ * @extends AbstractArray
+ * @implements MapInterface
*/
abstract class AbstractMap extends AbstractArray implements MapInterface
{
/**
- * Sets the given value to the given offset in the map.
- *
- * @param mixed $offset The offset to set.
- * @param mixed $value The value to set at the given offset.
- *
- * @throws InvalidArgumentException if the offset provided is `null`.
+ * @inheritDoc
*/
public function offsetSet($offset, $value): void
{
if ($offset === null) {
throw new InvalidArgumentException(
'Map elements are key/value pairs; a key must be provided for '
- . 'value ' . $value
+ . 'value ' . var_export($value, true)
);
}
@@ -48,9 +47,7 @@ public function offsetSet($offset, $value): void
}
/**
- * Returns `true` if this map contains a mapping for the specified key.
- *
- * @param mixed $key The key to check in the map.
+ * @inheritDoc
*/
public function containsKey($key): bool
{
@@ -58,11 +55,7 @@ public function containsKey($key): bool
}
/**
- * Returns `true` if this map maps one or more keys to the specified value.
- *
- * This performs a strict type check on the value.
- *
- * @param mixed $value The value to check in the map.
+ * @inheritDoc
*/
public function containsValue($value): bool
{
@@ -70,9 +63,7 @@ public function containsValue($value): bool
}
/**
- * Return an array of the keys contained in this map.
- *
- * @return mixed[]
+ * @inheritDoc
*/
public function keys(): array
{
@@ -80,14 +71,7 @@ public function keys(): array
}
/**
- * Returns the value to which the specified key is mapped, `null` if this
- * map contains no mapping for the key, or (optionally) `$defaultValue` if
- * this map contains no mapping for the key.
- *
- * @param mixed $key The key to return from the map.
- * @param mixed $defaultValue The default value to use if `$key` is not found.
- *
- * @return mixed|null the value or `null` if the key could not be found.
+ * @inheritDoc
*/
public function get($key, $defaultValue = null)
{
@@ -99,16 +83,7 @@ public function get($key, $defaultValue = null)
}
/**
- * Associates the specified value with the specified key in this map.
- *
- * If the map previously contained a mapping for the key, the old value is
- * replaced by the specified value.
- *
- * @param mixed $key The key to put or replace in the map.
- * @param mixed $value The value to store at `$key`.
- *
- * @return mixed|null the previous value associated with key, or `null` if
- * there was no mapping for `$key`.
+ * @inheritDoc
*/
public function put($key, $value)
{
@@ -119,17 +94,7 @@ public function put($key, $value)
}
/**
- * Associates the specified value with the specified key in this map only if
- * it is not already set.
- *
- * If there is already a value associated with `$key`, this returns that
- * value without replacing it.
- *
- * @param mixed $key The key to put in the map.
- * @param mixed $value The value to store at `$key`.
- *
- * @return mixed|null the previous value associated with key, or `null` if
- * there was no mapping for `$key`.
+ * @inheritDoc
*/
public function putIfAbsent($key, $value)
{
@@ -143,12 +108,7 @@ public function putIfAbsent($key, $value)
}
/**
- * Removes the mapping for a key from this map if it is present.
- *
- * @param mixed $key The key to remove from the map.
- *
- * @return mixed|null the previous value associated with key, or `null` if
- * there was no mapping for `$key`.
+ * @inheritDoc
*/
public function remove($key)
{
@@ -159,15 +119,7 @@ public function remove($key)
}
/**
- * Removes the entry for the specified key only if it is currently mapped to
- * the specified value.
- *
- * This performs a strict type check on the value.
- *
- * @param mixed $key The key to remove from the map.
- * @param mixed $value The value to match.
- *
- * @return bool true if the value was removed.
+ * @inheritDoc
*/
public function removeIf($key, $value): bool
{
@@ -181,14 +133,7 @@ public function removeIf($key, $value): bool
}
/**
- * Replaces the entry for the specified key only if it is currently mapped
- * to some value.
- *
- * @param mixed $key The key to replace.
- * @param mixed $value The value to set at `$key`.
- *
- * @return mixed|null the previous value associated with key, or `null` if
- * there was no mapping for `$key`.
+ * @inheritDoc
*/
public function replace($key, $value)
{
@@ -202,16 +147,7 @@ public function replace($key, $value)
}
/**
- * Replaces the entry for the specified key only if currently mapped to the
- * specified value.
- *
- * This performs a strict type check on the value.
- *
- * @param mixed $key The key to remove from the map.
- * @param mixed $oldValue The value to match.
- * @param mixed $newValue The value to use as a replacement.
- *
- * @return bool true if the value was replaced.
+ * @inheritDoc
*/
public function replaceIf($key, $oldValue, $newValue): bool
{
diff --git a/lib/collection/src/Map/AbstractTypedMap.php b/lib/collection/src/Map/AbstractTypedMap.php
index 80cec2e2..551d2e6c 100644
--- a/lib/collection/src/Map/AbstractTypedMap.php
+++ b/lib/collection/src/Map/AbstractTypedMap.php
@@ -21,6 +21,11 @@
/**
* This class provides a basic implementation of `TypedMapInterface`, to
* minimize the effort required to implement this interface.
+ *
+ * @template K
+ * @template T
+ * @extends AbstractMap
+ * @implements TypedMapInterface
*/
abstract class AbstractTypedMap extends AbstractMap implements TypedMapInterface
{
@@ -28,16 +33,22 @@ abstract class AbstractTypedMap extends AbstractMap implements TypedMapInterface
use ValueToStringTrait;
/**
- * Sets the given value to the given offset in the map.
+ * @param K|null $offset
+ * @param T $value
*
- * @param mixed $offset The offset to set.
- * @param mixed $value The value to set at the given offset.
+ * @inheritDoc
*
- * @throws InvalidArgumentException if the offset or value do not match the
- * expected types.
+ * @psalm-suppress MoreSpecificImplementedParamType
*/
public function offsetSet($offset, $value): void
{
+ if ($offset === null) {
+ throw new InvalidArgumentException(
+ 'Map elements are key/value pairs; a key must be provided for '
+ . 'value ' . var_export($value, true)
+ );
+ }
+
if ($this->checkType($this->getKeyType(), $offset) === false) {
throw new InvalidArgumentException(
'Key must be of type ' . $this->getKeyType() . '; key is '
@@ -52,6 +63,7 @@ public function offsetSet($offset, $value): void
);
}
+ /** @psalm-suppress MixedArgumentTypeCoercion */
parent::offsetSet($offset, $value);
}
}
diff --git a/lib/collection/src/Map/AssociativeArrayMap.php b/lib/collection/src/Map/AssociativeArrayMap.php
index f97e2172..79a314d9 100644
--- a/lib/collection/src/Map/AssociativeArrayMap.php
+++ b/lib/collection/src/Map/AssociativeArrayMap.php
@@ -16,6 +16,9 @@
/**
* `AssociativeArrayMap` represents a standard associative array object.
+ *
+ * @template T
+ * @extends AbstractMap
*/
class AssociativeArrayMap extends AbstractMap
{
diff --git a/lib/collection/src/Map/MapInterface.php b/lib/collection/src/Map/MapInterface.php
index 500bdb2d..6ed0b296 100644
--- a/lib/collection/src/Map/MapInterface.php
+++ b/lib/collection/src/Map/MapInterface.php
@@ -20,13 +20,16 @@
* An object that maps keys to values.
*
* A map cannot contain duplicate keys; each key can map to at most one value.
+ *
+ * @template T
+ * @extends ArrayInterface
*/
interface MapInterface extends ArrayInterface
{
/**
* Returns `true` if this map contains a mapping for the specified key.
*
- * @param mixed $key The key to check in the map.
+ * @param array-key $key The key to check in the map.
*/
public function containsKey($key): bool;
@@ -35,14 +38,15 @@ public function containsKey($key): bool;
*
* This performs a strict type check on the value.
*
- * @param mixed $value The value to check in the map.
+ * @param T $value The value to check in the map.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function containsValue($value): bool;
/**
* Return an array of the keys contained in this map.
*
- * @return mixed[]
+ * @return list
*/
public function keys(): array;
@@ -51,11 +55,12 @@ public function keys(): array;
* map contains no mapping for the key, or (optionally) `$defaultValue` if
* this map contains no mapping for the key.
*
- * @param mixed $key The key to return from the map.
- * @param mixed $defaultValue The default value to use if `$key` is not found.
+ * @param array-key $key The key to return from the map.
+ * @param T|null $defaultValue The default value to use if `$key` is not found.
*
- * @return mixed|null the value or `null` if the key could not be found.
+ * @return T|null the value or `null` if the key could not be found.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function get($key, $defaultValue = null);
/**
@@ -64,12 +69,13 @@ public function get($key, $defaultValue = null);
* If the map previously contained a mapping for the key, the old value is
* replaced by the specified value.
*
- * @param mixed $key The key to put or replace in the map.
- * @param mixed $value The value to store at `$key`.
+ * @param array-key $key The key to put or replace in the map.
+ * @param T $value The value to store at `$key`.
*
- * @return mixed|null the previous value associated with key, or `null` if
+ * @return T|null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function put($key, $value);
/**
@@ -79,22 +85,24 @@ public function put($key, $value);
* If there is already a value associated with `$key`, this returns that
* value without replacing it.
*
- * @param mixed $key The key to put in the map.
- * @param mixed $value The value to store at `$key`.
+ * @param array-key $key The key to put in the map.
+ * @param T $value The value to store at `$key`.
*
- * @return mixed|null the previous value associated with key, or `null` if
+ * @return T|null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function putIfAbsent($key, $value);
/**
* Removes the mapping for a key from this map if it is present.
*
- * @param mixed $key The key to remove from the map.
+ * @param array-key $key The key to remove from the map.
*
- * @return mixed|null the previous value associated with key, or `null` if
+ * @return T|null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function remove($key);
/**
@@ -103,23 +111,25 @@ public function remove($key);
*
* This performs a strict type check on the value.
*
- * @param mixed $key The key to remove from the map.
- * @param mixed $value The value to match.
+ * @param array-key $key The key to remove from the map.
+ * @param T $value The value to match.
*
* @return bool true if the value was removed.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function removeIf($key, $value): bool;
/**
* Replaces the entry for the specified key only if it is currently mapped
* to some value.
*
- * @param mixed $key The key to replace.
- * @param mixed $value The value to set at `$key`.
+ * @param array-key $key The key to replace.
+ * @param T $value The value to set at `$key`.
*
- * @return mixed|null the previous value associated with key, or `null` if
+ * @return T|null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function replace($key, $value);
/**
@@ -128,11 +138,12 @@ public function replace($key, $value);
*
* This performs a strict type check on the value.
*
- * @param mixed $key The key to remove from the map.
- * @param mixed $oldValue The value to match.
- * @param mixed $newValue The value to use as a replacement.
+ * @param array-key $key The key to remove from the map.
+ * @param T $oldValue The value to match.
+ * @param T $newValue The value to use as a replacement.
*
* @return bool true if the value was replaced.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function replaceIf($key, $oldValue, $newValue): bool;
}
diff --git a/lib/collection/src/Map/NamedParameterMap.php b/lib/collection/src/Map/NamedParameterMap.php
index 7adfa0af..9926ddd8 100644
--- a/lib/collection/src/Map/NamedParameterMap.php
+++ b/lib/collection/src/Map/NamedParameterMap.php
@@ -25,6 +25,8 @@
/**
* `NamedParameterMap` represents a mapping of values to a set of named keys
* that may optionally be typed
+ *
+ * @extends AbstractMap
*/
class NamedParameterMap extends AbstractMap
{
@@ -34,15 +36,15 @@ class NamedParameterMap extends AbstractMap
/**
* Named parameters defined for this map.
*
- * @var array
+ * @var array
*/
protected $namedParameters;
/**
* Constructs a new `NamedParameterMap`.
*
- * @param array $namedParameters The named parameters defined for this map.
- * @param mixed[] $data An initial set of data to set on this map.
+ * @param array $namedParameters The named parameters defined for this map.
+ * @param array $data An initial set of data to set on this map.
*/
public function __construct(array $namedParameters, array $data = [])
{
@@ -53,7 +55,7 @@ public function __construct(array $namedParameters, array $data = [])
/**
* Returns named parameters set for this `NamedParameterMap`.
*
- * @return array
+ * @return array
*/
public function getNamedParameters(): array
{
@@ -61,17 +63,17 @@ public function getNamedParameters(): array
}
/**
- * Sets the given value to the given offset in the map.
- *
- * @param mixed $offset The offset to set.
- * @param mixed $value The value to set at the given offset.
- *
- * @throws InvalidArgumentException if the offset provided is not a
- * defined named parameter, or if the value is not of the type defined
- * for the given named parameter.
+ * @inheritDoc
*/
public function offsetSet($offset, $value): void
{
+ if ($offset === null) {
+ throw new InvalidArgumentException(
+ 'Map elements are key/value pairs; a key must be provided for '
+ . 'value ' . var_export($value, true)
+ );
+ }
+
if (!array_key_exists($offset, $this->namedParameters)) {
throw new InvalidArgumentException(
'Attempting to set value for unconfigured parameter \''
@@ -94,9 +96,9 @@ public function offsetSet($offset, $value): void
* Given an array of named parameters, constructs a proper mapping of
* named parameters to types.
*
- * @param array $namedParameters The named parameters to filter.
+ * @param array $namedParameters The named parameters to filter.
*
- * @return array
+ * @return array
*/
protected function filterNamedParameters(array $namedParameters): array
{
@@ -105,11 +107,11 @@ protected function filterNamedParameters(array $namedParameters): array
foreach ($namedParameters as $key => $value) {
if (is_int($key)) {
- $names[] = (string) $value;
+ $names[] = $value;
$types[] = 'mixed';
} else {
$names[] = $key;
- $types[] = (string) $value;
+ $types[] = $value;
}
}
diff --git a/lib/collection/src/Map/TypedMap.php b/lib/collection/src/Map/TypedMap.php
index 84d075f8..2e796377 100644
--- a/lib/collection/src/Map/TypedMap.php
+++ b/lib/collection/src/Map/TypedMap.php
@@ -79,6 +79,10 @@
* }
* }
* ```
+ *
+ * @template K
+ * @template T
+ * @extends AbstractTypedMap
*/
class TypedMap extends AbstractTypedMap
{
@@ -97,7 +101,7 @@ class TypedMap extends AbstractTypedMap
/**
* The data type of values stored in this collection.
*
- * A map values's type is immutable once it is set. For this reason, this
+ * A map value's type is immutable once it is set. For this reason, this
* property is set private.
*
* @var string data type of the map value.
@@ -110,26 +114,22 @@ class TypedMap extends AbstractTypedMap
*
* @param string $keyType The data type of the map's keys.
* @param string $valueType The data type of the map's values.
- * @param mixed[] $data The initial data to set for this map.
+ * @param array $data The initial data to set for this map.
*/
public function __construct(string $keyType, string $valueType, array $data = [])
{
$this->keyType = $keyType;
$this->valueType = $valueType;
+
+ /** @psalm-suppress MixedArgumentTypeCoercion */
parent::__construct($data);
}
- /**
- * Return the type used on the key.
- */
public function getKeyType(): string
{
return $this->keyType;
}
- /**
- * Return the type forced on the values.
- */
public function getValueType(): string
{
return $this->valueType;
diff --git a/lib/collection/src/Map/TypedMapInterface.php b/lib/collection/src/Map/TypedMapInterface.php
index 54c78369..0308109c 100644
--- a/lib/collection/src/Map/TypedMapInterface.php
+++ b/lib/collection/src/Map/TypedMapInterface.php
@@ -17,6 +17,9 @@
/**
* A `TypedMapInterface` represents a map of elements where key and value are
* typed.
+ *
+ * @template T
+ * @extends MapInterface
*/
interface TypedMapInterface extends MapInterface
{
diff --git a/lib/collection/src/Queue.php b/lib/collection/src/Queue.php
index 4f53ff5e..93e032b4 100644
--- a/lib/collection/src/Queue.php
+++ b/lib/collection/src/Queue.php
@@ -22,6 +22,10 @@
/**
* This class provides a basic implementation of `QueueInterface`, to minimize
* the effort required to implement this interface.
+ *
+ * @template T
+ * @extends AbstractArray
+ * @implements QueueInterface
*/
class Queue extends AbstractArray implements QueueInterface
{
@@ -50,7 +54,7 @@ class Queue extends AbstractArray implements QueueInterface
* specified data.
*
* @param string $queueType The type (FQCN) associated with this queue.
- * @param mixed[] $data The initial items to store in the collection.
+ * @param array $data The initial items to store in the collection.
*/
public function __construct(string $queueType, array $data = [])
{
@@ -59,19 +63,11 @@ public function __construct(string $queueType, array $data = [])
}
/**
- * Sets the given value to the given offset in the queue.
+ * {@inheritDoc}
*
* Since arbitrary offsets may not be manipulated in a queue, this method
* serves only to fulfill the `ArrayAccess` interface requirements. It is
* invoked by other operations when adding values to the queue.
- *
- * @link http://php.net/manual/en/arrayaccess.offsetset.php ArrayAccess::offsetSet()
- *
- * @param mixed|null $offset The offset is ignored and is treated as `null`.
- * @param mixed $value The value to set at the given offset.
- *
- * @throws InvalidArgumentException when the value does not match the
- * specified type for this queue.
*/
public function offsetSet($offset, $value): void
{
@@ -86,19 +82,7 @@ public function offsetSet($offset, $value): void
}
/**
- * Ensures that this queue contains the specified element.
- *
- * This method differs from `offer()` only in that it throws an exception if
- * it cannot add the element to the queue.
- *
- * @see self::offer()
- *
- * @param mixed $element The element to add to this queue.
- *
- * @return bool `true` if this queue changed as a result of the call.
- *
- * @throws InvalidArgumentException when the element does not match the
- * specified type for this queue.
+ * @inheritDoc
*/
public function add($element): bool
{
@@ -108,39 +92,23 @@ public function add($element): bool
}
/**
- * Retrieves, but does not remove, the head of this queue.
- *
- * This method differs from `peek()` only in that it throws an exception if
- * this queue is empty.
- *
- * @see self::peek()
- *
- * @return mixed the head of this queue.
- *
- * @throws NoSuchElementException if this queue is empty.
+ * @inheritDoc
*/
public function element()
{
- if ($this->count() === 0) {
+ $element = $this->peek();
+
+ if ($element === null) {
throw new NoSuchElementException(
'Can\'t return element from Queue. Queue is empty.'
);
}
- return $this[$this->index];
+ return $element;
}
/**
- * Inserts the specified element into this queue.
- *
- * This method differs from `add()` only in that it does not throw an
- * exception if it cannot add the element to the queue.
- *
- * @see self::add()
- *
- * @param mixed $element The element to add to this queue.
- *
- * @return bool `true` if the element was added to this queue, else `false`.
+ * @inheritDoc
*/
public function offer($element): bool
{
@@ -152,12 +120,7 @@ public function offer($element): bool
}
/**
- * Retrieves, but does not remove, the head of this queue, or returns `null`
- * if this queue is empty.
- *
- * @see self::element()
- *
- * @return mixed|null the head of this queue, or `null` if this queue is empty.
+ * @inheritDoc
*/
public function peek()
{
@@ -169,12 +132,7 @@ public function peek()
}
/**
- * Retrieves and removes the head of this queue, or returns `null`
- * if this queue is empty.
- *
- * @see self::remove()
- *
- * @return mixed|null the head of this queue, or `null` if this queue is empty.
+ * @inheritDoc
*/
public function poll()
{
@@ -191,34 +149,19 @@ public function poll()
}
/**
- * Retrieves and removes the head of this queue.
- *
- * This method differs from `poll()` only in that it throws an exception if
- * this queue is empty.
- *
- * @see self::poll()
- *
- * @return mixed the head of this queue.
- *
- * @throws NoSuchElementException if this queue is empty.
+ * @inheritDoc
*/
public function remove()
{
- if ($this->count() === 0) {
+ $head = $this->poll();
+
+ if ($head === null) {
throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.');
}
- $head = $this[$this->index];
-
- unset($this[$this->index]);
- $this->index++;
-
return $head;
}
- /**
- * Returns the type associated with this queue.
- */
public function getType(): string
{
return $this->queueType;
diff --git a/lib/collection/src/QueueInterface.php b/lib/collection/src/QueueInterface.php
index 6c7f2ac2..8c7383df 100644
--- a/lib/collection/src/QueueInterface.php
+++ b/lib/collection/src/QueueInterface.php
@@ -92,6 +92,9 @@
* Even in the implementations that permit it, `null` should not be inserted
* into a queue, as `null` is also used as a special return value by the
* `poll()` method to indicate that the queue contains no elements.
+ *
+ * @template T
+ * @extends ArrayInterface
*/
interface QueueInterface extends ArrayInterface
{
@@ -116,7 +119,7 @@ interface QueueInterface extends ArrayInterface
*
* @see self::offer()
*
- * @param mixed $element The element to add to this queue.
+ * @param T $element The element to add to this queue.
*
* @return bool `true` if this queue changed as a result of the call.
*
@@ -125,6 +128,7 @@ interface QueueInterface extends ArrayInterface
* Implementations should use a more-specific exception that extends
* `\RuntimeException`.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function add($element): bool;
/**
@@ -135,7 +139,7 @@ public function add($element): bool;
*
* @see self::peek()
*
- * @return mixed the head of this queue.
+ * @return T the head of this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
@@ -151,10 +155,11 @@ public function element();
*
* @see self::add()
*
- * @param mixed $element The element to add to this queue.
+ * @param T $element The element to add to this queue.
*
* @return bool `true` if the element was added to this queue, else `false`.
*/
+ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function offer($element): bool;
/**
@@ -163,7 +168,7 @@ public function offer($element): bool;
*
* @see self::element()
*
- * @return mixed|null the head of this queue, or `null` if this queue is empty.
+ * @return T|null the head of this queue, or `null` if this queue is empty.
*/
public function peek();
@@ -173,7 +178,7 @@ public function peek();
*
* @see self::remove()
*
- * @return mixed|null the head of this queue, or `null` if this queue is empty.
+ * @return T|null the head of this queue, or `null` if this queue is empty.
*/
public function poll();
@@ -185,7 +190,7 @@ public function poll();
*
* @see self::poll()
*
- * @return mixed the head of this queue.
+ * @return T the head of this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
diff --git a/lib/collection/src/Set.php b/lib/collection/src/Set.php
index 42fb66c3..6932f247 100644
--- a/lib/collection/src/Set.php
+++ b/lib/collection/src/Set.php
@@ -34,6 +34,9 @@
* $bar = new \My\Foo();
* $set->add($bar); // returns TRUE, $bar !== $foo
* ```
+ *
+ * @template T
+ * @extends AbstractSet
*/
class Set extends AbstractSet
{
@@ -51,7 +54,7 @@ class Set extends AbstractSet
* specified data.
*
* @param string $setType The type (FQCN) associated with this set.
- * @param mixed[] $data The initial items to store in the set.
+ * @param array $data The initial items to store in the set.
*/
public function __construct(string $setType, array $data = [])
{
@@ -59,9 +62,6 @@ public function __construct(string $setType, array $data = [])
parent::__construct($data);
}
- /**
- * Returns the type associated with this set.
- */
public function getType(): string
{
return $this->setType;
diff --git a/lib/collection/src/Tool/ValueExtractorTrait.php b/lib/collection/src/Tool/ValueExtractorTrait.php
index 7bc4878d..f9be1be2 100644
--- a/lib/collection/src/Tool/ValueExtractorTrait.php
+++ b/lib/collection/src/Tool/ValueExtractorTrait.php
@@ -29,7 +29,7 @@ trait ValueExtractorTrait
/**
* Extracts the value of the given property or method from the object.
*
- * @param object $object The object to extract the value from.
+ * @param mixed $object The object to extract the value from.
* @param string $propertyOrMethod The property or method for which the
* value should be extracted.
*
@@ -37,8 +37,12 @@ trait ValueExtractorTrait
*
* @throws ValueExtractionException if the method or property is not defined.
*/
- protected function extractValue(object $object, string $propertyOrMethod)
+ protected function extractValue($object, string $propertyOrMethod)
{
+ if (!is_object($object)) {
+ throw new ValueExtractionException('Unable to extract a value from a non-object');
+ }
+
if (property_exists($object, $propertyOrMethod)) {
return $object->$propertyOrMethod;
}
diff --git a/lib/collection/src/Tool/ValueToStringTrait.php b/lib/collection/src/Tool/ValueToStringTrait.php
index 34a9a0a6..721ade00 100644
--- a/lib/collection/src/Tool/ValueToStringTrait.php
+++ b/lib/collection/src/Tool/ValueToStringTrait.php
@@ -71,7 +71,12 @@ protected function toolValueToString($value): string
return '(' . get_resource_type($value) . ' resource #' . (int) $value . ')';
}
- // after this line $value is an object since is not null, scalar, array or resource
+ // If we don't know what it is, use var_export().
+ if (!is_object($value)) {
+ return '(' . var_export($value, true) . ')';
+ }
+
+ // From here, $value should be an object.
// __toString() is implemented
if (is_callable([$value, '__toString'])) {
diff --git a/lib/comment.inc.php b/lib/comment.inc.php
index 7ac49fbe..639271b2 100755
--- a/lib/comment.inc.php
+++ b/lib/comment.inc.php
@@ -34,12 +34,19 @@ function showComment($_detail_id)
global $dbs;
require SIMBIO.'simbio_GUI/paging/simbio_paging.inc.php';
$_list_comment = '';
+ if (!is_null(config('3rd_party_comment')))
+ {
+ // execute registered hook for 3rd party comment management
+ \SLiMS\Plugins::getInstance()->execute('comment_init', [&$_list_comment]);
+ return $_list_comment;
+ }
$_recs_each_page = 3;
$_pages_each_set = 10;
$_all_recs = 0;
+ $_detail_id = (int)$_detail_id;
if (ISSET($_GET['page']) && $_GET['page']>1) {
- $page = $_GET['page'];
+ $page = (int)$_GET['page'];
} else {
$page = 1;
}
@@ -60,7 +67,7 @@ function showComment($_detail_id)
while ($_data = $commlist->fetch_assoc()) {
$_list_comment .= '
';
$_list_comment .= '
'.$_data['member_name']. __(' at ') . $_data['input_date']. __(' write'). '