diff --git a/src/configs/Config.php b/src/configs/Config.php index f02b2f5d9..49979cfad 100755 --- a/src/configs/Config.php +++ b/src/configs/Config.php @@ -1225,5 +1225,3 @@ nsconddefine('SENTENCE_COMPRESSION_ENABLED', false); nsconddefine('NUM_LEX_BULK_INSERTS',100000); /** Length of advertisement credits service account id string*/ nsconddefine('AD_CREDITS_SERVICE_ACCOUNT_LEN', 32); -/** Type used to indicate recommendation scheme */ -nsdefine('RECOMMENDATION_TYPE', 1); \ No newline at end of file diff --git a/src/controllers/components/SocialComponent.php b/src/controllers/components/SocialComponent.php index 6f5e06086..23099bd43 100644 --- a/src/controllers/components/SocialComponent.php +++ b/src/controllers/components/SocialComponent.php @@ -3201,13 +3201,27 @@ class SocialComponent extends Component implements CrawlConstants $_SESSION["MAX_PAGES_TO_SHOW"] > 0) ? $_SESSION["MAX_PAGES_TO_SHOW"] : C\DEFAULT_ADMIN_PAGING_NUM; + array_pop($data['sort_fields']); + array_pop($data['sort_fields']); + if (!empty($_REQUEST['sort']) && in_array($_REQUEST['sort'], + array_keys($data['sort_fields']))) { + if (!empty($_SESSION['media_sorts']) && + count($_SESSION['media_sorts']) > 10) { + $first_key = array_key_first( + $_SESSION['media_sorts']); + unset($_SESSION['media_sorts'][$first_key]); + } + $_SESSION['media_sorts']['pages'] = $_REQUEST['sort']; + } + $data['CURRENT_SORT'] = $_SESSION['media_sorts']["pages"] + ?? ""; $filter = (empty($filter)) ? "" : $filter; if (isset($page_name)) { $data['PAGE_NAME'] = $page_name; } $data["LIMIT"] = $limit; $data["RESULTS_PER_PAGE"] = $num; - $data["FILTER"] = preg_replace("/\s+/u", " ", $filter);; + $data["FILTER"] = preg_replace("/\s+/u", " ", $filter); $filter = preg_replace("/\s+/u", "_", $filter); $search_page_info = false; if ($filter != "") { @@ -3219,7 +3233,7 @@ class SocialComponent extends Component implements CrawlConstants list($data["TOTAL_ROWS"], $data["PAGES"]) = $group_model->getPageList( $group_id, $data['CURRENT_LOCALE_TAG'], $filter, - $limit, $num); + $data['CURRENT_SORT'], $limit, $num); } else { $data["MODE"] = "read"; $page_name = $data["FILTER"]; @@ -3754,12 +3768,11 @@ EOD; $folder_hash_id = L\crawlHash(($data['PAGE_ID'] ?? -1) . $sub_path); if (!empty($_REQUEST['sort']) && in_array($_REQUEST['sort'], array_keys($data['sort_fields']))) { - unset($_SESSION['media_sorts'][$folder_hash_id]); - $_SESSION['media_sorts'][$folder_hash_id] = $_REQUEST['sort']; if (count($_SESSION['media_sorts']) > 10) { $first_key = array_key_first($_SESSION['media_sorts']); unset($_SESSION['media_sorts'][$first_key]); } + $_SESSION['media_sorts'][$folder_hash_id] = $_REQUEST['sort']; $changed = true; } if (!empty($_REQUEST['layout']) && in_array($_REQUEST['layout'], diff --git a/src/css/search.css b/src/css/search.css index b6e3e1a68..e4b95265e 100755 --- a/src/css/search.css +++ b/src/css/search.css @@ -4098,6 +4098,7 @@ td.instruct padding: 0; margin: 0; } +#page-sort-fields, #group-sort { position: relative; diff --git a/src/library/VersionFunctions.php b/src/library/VersionFunctions.php index b37a3b902..cf7f2c0d1 100644 --- a/src/library/VersionFunctions.php +++ b/src/library/VersionFunctions.php @@ -2025,17 +2025,6 @@ function upgradeDatabaseVersion72(&$db) */ function upgradeDatabaseVersion73(&$db) { - $dbinfo = ["DBMS" => C\DBMS, "DB_HOST" => C\DB_HOST, - "DB_NAME" => C\DB_NAME, "DB_PASSWORD" => C\DB_PASSWORD]; - $integer = $db->integerType($dbinfo); - $db->execute("DROP TABLE IF EXISTS USER_TERM_WEIGHTS_HASH2VEC"); - $db->execute("DROP TABLE IF EXISTS USER_ITEM_SIMILARITY_HASH2VEC"); - $db->execute("DROP TABLE IF EXISTS HASH2VEC_TERM_SIMILARITY"); - $db->execute("CREATE TABLE USER_TERM_WEIGHTS_HASH2VEC(TERM_ID $integer, - USER_ID $integer, WEIGHT FLOAT, PRIMARY KEY(TERM_ID, USER_ID))"); - $db->execute("CREATE TABLE USER_ITEM_SIMILARITY_HASH2VEC(USER_ID $integer, - THREAD_ID $integer, SIMILARITY FLOAT, GROUP_MEMBER $integer, - PRIMARY KEY(USER_ID, THREAD_ID))"); - $db->execute("CREATE TABLE HASH2VEC_TERM_SIMILARITY(TERM1 $integer, - TERM2 $integer, SCORE FLOAT, PRIMARY KEY(TERM1, TERM2))"); + $db->execute("ALTER TABLE GROUP_PAGE ADD COLUMN LAST_MODIFIED + NUMERIC(".C\TIMESTAMP_LEN.")"); } diff --git a/src/library/media_jobs/RecommendationJob.php b/src/library/media_jobs/RecommendationJob.php index db6758dc2..d451c610b 100644 --- a/src/library/media_jobs/RecommendationJob.php +++ b/src/library/media_jobs/RecommendationJob.php @@ -33,6 +33,7 @@ namespace seekquarry\yioop\library\media_jobs; use seekquarry\yioop\configs as C; use seekquarry\yioop\library as L; +use seekquarry\yioop\library\LinearAlgebra as LinearAlgebra; use seekquarry\yioop\library\CrawlConstants; use seekquarry\yioop\models\CronModel; @@ -76,36 +77,6 @@ class RecommendationJob extends MediaJob * Maximum number of terms used in making recommendations */ const MAX_TERMS = 20000; - /** - * Regular expression pattern to clean words in data corpus - * for HASH2VEC approach - */ - const WORD_SPLIT_PATTERN = "/([.,!?\"':;)(])+/"; - /** - * Dimensions of HASH2VEC vectors generated for words in data corpus - */ - const HASH2VEC_VECTOR_LENGTH = 200; - /** - * Length of context window for HASH2VEC similarity calculation - */ - const CONTEXT_WIDTH = 5; - /** - * Number of similar words to keep for a word in HASH2VEC - */ - const MAX_SIMILAR_WORDS = 10; - /** - * Array of the generated HASH2VEC vectors for words in the data corpus - * @var array - */ - public $hash2vec_vectors = []; - /** - * Associative array of words in the data corpus for HASH2VEC - */ - public $hash2vec_words_dictionary = []; - /** - * Associative array of number of word in the data corpus for HASH2VEC - */ - public $hash2vec_words_reverse_dictionary = []; /** * Sets up the database connection so can access tables related * to recommendations. Initialize timing info related to job. @@ -230,9 +201,6 @@ class RecommendationJob extends MediaJob $this->computeUserItemIdf($number_items, $number_users); $this->tfIdfUsers(); $this->tfIdfItems(); - if (C\RECOMMENDATION_TYPE == 1) { - $this->initializeHash2Vec(); - } $this->computeUserItemSimilarity(); $not_belongs_subselect = "NOT EXISTS (SELECT * FROM ". "GROUP_ITEM B WHERE S.USER_ID=B.USER_ID ". @@ -524,32 +492,14 @@ class RecommendationJob extends MediaJob { L\crawlLog("...Computing User Item Similarity Scores."); $db = $this->db; - $user_weight_table = "USER_TERM_WEIGHTS"; - if (C\RECOMMENDATION_TYPE == 1) { - $this->db->execute("INSERT INTO USER_TERM_WEIGHTS_HASH2VEC - SELECT * FROM USER_TERM_WEIGHTS"); - $this->db->execute("UPDATE USER_TERM_WEIGHTS_HASH2VEC - SET WEIGHT=COALESCE((SELECT SUM(HTS.SCORE*UTW.WEIGHT) - FROM HASH2VEC_TERM_SIMILARITY HTS, USER_TERM_WEIGHTS UTW - WHERE HTS.TERM2 = UTW.TERM_ID AND HTS.TERM1 = - USER_TERM_WEIGHTS_HASH2VEC.TERM_ID),0)"); - $this->db->execute("UPDATE USER_TERM_WEIGHTS_HASH2VEC - SET WEIGHT=WEIGHT+COALESCE((SELECT UTW.WEIGHT - FROM USER_TERM_WEIGHTS UTW WHERE - USER_TERM_WEIGHTS_HASH2VEC.TERM_ID = UTW.TERM_ID - AND USER_TERM_WEIGHTS_HASH2VEC.USER_ID = UTW.USER_ID),0)"); - $this->db->execute("INSERT INTO USER_ITEM_SIMILARITY_HASH2VEC - SELECT * FROM USER_ITEM_SIMILARITY"); - $user_weight_table = "USER_TERM_WEIGHTS_HASH2VEC"; - } $similarity_parts_sql = "SELECT SUM(UTW.WEIGHT * ITW.WEIGHT) AS THREAD_DOT_USER, ". "SUM(UTW.WEIGHT * UTW.WEIGHT) AS USER_MAG," . "SUM(ITW.WEIGHT * ITW.WEIGHT) AS ITEM_MAG," . "GI.PARENT_ID AS THREAD_ID, UTW.USER_ID AS USER_ID ". - "FROM ITEM_TERM_WEIGHTS ITW, $user_weight_table UTW, ". - "GROUP_ITEM GI WHERE GI.ID = ITW.ITEM_ID AND ". - "UTW.TERM_ID=ITW.TERM_ID GROUP BY UTW.USER_ID, GI.PARENT_ID"; + "FROM ITEM_TERM_WEIGHTS ITW, USER_TERM_WEIGHTS UTW, GROUP_ITEM GI ". + "WHERE GI.ID = ITW.ITEM_ID AND UTW.TERM_ID=ITW.TERM_ID " . + "GROUP BY UTW.USER_ID, GI.PARENT_ID"; $similarity_parts_result = $db->execute($similarity_parts_sql); //used to check if belong to group $member_info_sql = "SELECT GI.GROUP_ID FROM ". @@ -669,302 +619,4 @@ class RecommendationJob extends MediaJob $db->execute($insert_ignore_sql); } } - /** - * Initializes data corpus for HASH2VEC recommendation approach. The data - * consists of concated title and description text of the group items - * separated by new line character for previous item - */ - public function initializeHash2Vec() - { - L\crawlLog("...Initializing Hash2Vec."); - $db = $this->db; - $data_corpus = ""; - $group_item_sql = "SELECT ID AS ITEM_ID, TITLE, DESCRIPTION ". - "FROM GROUP_ITEM ". - "WHERE LOWER(TITLE) NOT LIKE '%page%'" . - "AND LOWER(DESCRIPTION) NOT LIKE '%-0700%'" . - "ORDER BY PUBDATE DESC " . $db->limitOffset(self::MAX_GROUP_ITEMS); - $results = $db->execute($group_item_sql); - while ($item = $db->fetchArray($results)) { - $data_corpus .= $item['TITLE']. " "; - $data_corpus .= $item['DESCRIPTION'] . "\n"; - } - $this->generateVectors($data_corpus); - } - /** - * Generates the HASH2VEC vectors for words in the given data corpus - * - * @param string $data_corpus the data corpus of group items in the form - * title + description for each item per line - */ - public function generateVectors($data_corpus) - { - L\crawlLog("...Generating Hash2Vec Vectors."); - for ($i=0; $i<self::CONTEXT_WIDTH; $i++) { - $context_distance_vector[] = -$i + self::CONTEXT_WIDTH; - } - for ($i=0; $i<self::CONTEXT_WIDTH; $i++) { - $context_distance_vector[] = $i; - } - $standard_deviation = $this->calculateStandardDeviation( - $context_distance_vector); - $word_id = 0; - $data_lines = explode("\n", strtolower($data_corpus)); - foreach ($data_lines as $line) { - $line = preg_replace("/[\n\r]/",'',$line); - if (strlen($line) == 0) { - continue; - } - $words = explode(" ", strtolower($line)); - if (count($words) <= 1) { continue; } - $clean_words = []; - foreach ($words as $word) { - if ($word){ - $clean_word = preg_replace( - self::WORD_SPLIT_PATTERN, '', $word); - $clean_words[] = $clean_word; - } - } - $word_ids = []; - foreach ($clean_words as $word) { - $word_ids[] = $this->wordToId($word, $word_id); - $word_id += 1; - } - $word_index = 0; - foreach ($word_ids as $id) { - list($context_words, $distances) = - $this->getContextWords($word_ids, $word_index); - $i = 0; - foreach ($context_words as $word) { - $power = pow($distances[$i] / $standard_deviation, 2); - $distance = exp(-1 * $power); - list($index, $sign) = $this->getHashIndex( - $this->hash2vec_words_reverse_dictionary[$word]); - $this->hash2vec_vectors[$id][$index] = - $this->hash2vec_vectors[$id][$index] + - $sign * $distance; - $i += 1; - } - $word_index += 1; - } - } - $this->normalizeVectors(); - $this->calculateSimilarityHash2Vec(); - } - /** - * Performs normalization for the HASH2VEC vectors in order to avoid - * the features with less values getting neglected in calculating - * similarity - */ - public function normalizeVectors() - { - L\crawlLog("...Normalizing Hash2Vec Vectors."); - for ($i = 0; $i < count($this->hash2vec_vectors); $i++) { - $vector = []; - foreach ($this->hash2vec_vectors[$i] as $value) { - $vector[] = abs($value); - } - $sum = array_sum($vector); - foreach ($vector as $index=>$value) { - if ($sum == 0) { $sum = 1; } - $this->hash2vec_vectors[$i][$index] = $value * 1. / $sum; - } - } - } - /** - * Calculates top 10 similar words for every word in the hash2vec words - * dictionary. The similarity is calculated using cosine coefficient - * between the corresponding vectors of two words - */ - public function calculateSimilarityHash2Vec() - { - L\crawlLog("...Generating Hash2Vec Similarity Score."); - $db=$this->db; - $base_sql = "INSERT INTO HASH2VEC_TERM_SIMILARITY VALUES"; - $insert_sql = $base_sql; - $insert_count = 0; - $comma = ""; - foreach ($this->hash2vec_words_reverse_dictionary as $id => $word) { - $similar_words = $this->getSimilarWords($word); - if (!empty($similar_words)) { - $word_hash = floor(bindec(str_replace(" ", "", - L\toBinString(hash("crc32b", strtolower($word), true))))/2); - $db->execute("DELETE FROM HASH2VEC_TERM_SIMILARITY WHERE - TERM1 = " . $word_hash); - foreach ($similar_words as $item) { - $term_hash = floor(bindec(str_replace(" ", "", - L\toBinString(hash("crc32b", $item[0], true))))/2); - $score = $item[1]; - $insert_sql .= "$comma ($word_hash, $term_hash, - $score)"; - $comma = ","; - $insert_count++; - if ($insert_count == self::BATCH_SQL_INSERT_NUM) { - $insert_ignore_sql = $db->insertIgnore($insert_sql); - $db->execute($insert_ignore_sql); - $insert_sql = $base_sql; - $insert_count = 0; - $comma = ""; - } - } - } - } - if ($insert_count > 0) { - $insert_ignore_sql = $db->insertIgnore($insert_sql); - $db->execute($insert_ignore_sql); - } - } - /** - * Calculates hash score for all the words in the dictionary with given - * word and finds defined number of similar words - * - * @param string $word word for which similar words are to be calculated - * @return array array of similar words and similarity score - */ - public function getSimilarWords($word) { - $word = strtolower($word); - if (!array_key_exists($word, $this->hash2vec_words_dictionary)) { - return NULL; - } - $word_id = $this->hash2vec_words_dictionary[$word]; - $word_vector = $this->hash2vec_vectors[$word_id]; - $heap = new \SplMinHeap(); - foreach ($this->hash2vec_vectors as $index => $vector) { - if ($index == $word_id) { - continue; - } - $power = []; - foreach ($vector as $value) { - $power[] = pow($value, 2); - }; - $sum = array_sum($power); - if ($sum == 0) { - $sum = 1; - } - $root_sum = sqrt($sum); - $score = $this->dotProduct($word_vector, $vector) / $root_sum; - if ($heap->count() < self::MAX_SIMILAR_WORDS) { - $heap->insert([$score, $index]); - } else if ($heap->top()[0] < $score) { - $heap->extract(); - $heap->insert([$score, $index]); - } - } - $similar_words = []; - for ($heap->top(); $heap->valid(); $heap->next()) { - $word = $heap->current(); - array_push($similar_words, [$this->hash2vec_words_reverse_dictionary - [$word[1]], $word[0]]); - } - return $similar_words; - } - /** - * Calculates statistical standard deviation of given data elements - * - * @param array $data array of elements - * @return float standard deviation - */ - public function calculateStandardDeviation($data) - { - $average = round(array_sum($data) / count($data), 1); - $differences = []; - foreach ($data as $value) { - $difference = $value - $average; - $differences[] = pow($difference, 2); - } - $sum = array_sum($differences); - $variance = $sum / count($differences); - $standard_deviation = sqrt($variance); - return $standard_deviation; - } - /** - * Insert a word to dictionary of words and assign a id - * - * @param string $word word to insert into dictionary - * @param int $id id of the word - * @return int id assigned to the word - */ - public function wordToId($word, $id) - { - if (!array_key_exists($word, $this->hash2vec_words_dictionary)) { - $this->hash2vec_words_dictionary[$word] = $id; - $this->hash2vec_words_reverse_dictionary[$id] = $word; - $this->hash2vec_vectors[] = - array_fill(0,self::HASH2VEC_VECTOR_LENGTH,0); - } - return $this->hash2vec_words_dictionary[$word]; - } - /** - * Generates appropriate context window of words for the given word - * - * @param array $word_ids ids of words in current data line - * @param int index the index of word for which the context window is - * calculated - * @return array of context words and their distances from given word - */ - public function getContextWords($word_ids, $index) { - $start_idx = 0; - $end_idx = $index + 1 + self::CONTEXT_WIDTH; - if ($index > self::CONTEXT_WIDTH) { - $start_idx = $index - self::CONTEXT_WIDTH; - } - if ($end_idx > count($word_ids)) { $end_idx = count($word_ids); } - $prefix = array_slice($word_ids, $start_idx, $index-$start_idx); - if ($index <= self::CONTEXT_WIDTH) { - $suffix = array_slice($word_ids,$index + 1, - $end_idx - $index - $start_idx - 1); - } else if ($index >= (count($word_ids) - self::CONTEXT_WIDTH)) { - $suffix = array_slice($word_ids, $index + 1, $end_idx - $index - 1); - } else { - $suffix = array_slice($word_ids, $index + 1, self::CONTEXT_WIDTH); - } - $context_words = array_merge($prefix, $suffix); - if ($index - $start_idx == 0) { - $prefix = []; - } else { - $prefix = range(1, $index - $start_idx); - } - if ($index <= self::CONTEXT_WIDTH) { - if ($end_idx - $index - $start_idx - 1 == 0) { - $suffix = []; - } else { - $suffix = range($end_idx - $index - $start_idx - 1, 1, -1); - } - } else if ($index == count($word_ids) - 1) { - $suffix = []; - } else { - $suffix = range($end_idx - $index - 1, 1, -1); - } - $distance = array_merge($prefix, $suffix); - assert(count($distance) == count($context_words)); - return [$context_words, $distance]; - } - /** - * Calculates the index in HASH2VEC vector of the word where the given - * word's context should be written using md5 hash value of the given word - * - * @param string $word whose hash value is to be calculated - * @return array of index and sign of hash - */ - public function getHashIndex($word) - { - $hash = unpack("N", substr(md5($word), 0, 4))[1]; - $index = $hash % self::HASH2VEC_VECTOR_LENGTH; - $sign = $hash % 2 ? 1 : -1; - return [$index, $sign]; - } - /** - * Performs dot product operation on two vectors - * - * @param array $vector1 array representing vector 1 elements - * @param array $vector2 array representing vector 2 elements - * @return float product of two vectors - */ - public function dotProduct($vector1, $vector2) { - $product = 0; - for ($i = 0; $i < count($vector1); $i++) { - $product = $product + $vector1[$i] * $vector2[$i]; - } - return $product; - } } diff --git a/src/models/GroupModel.php b/src/models/GroupModel.php index e861e1ef0..99885a5ae 100644 --- a/src/models/GroupModel.php +++ b/src/models/GroupModel.php @@ -1437,16 +1437,16 @@ class GroupModel extends Model implements MediaConstants //can only add and use resources for a page that exists $parsed_page = $this->insertResourcesParsePage($group_id, $page_id, $locale_tag, $parsed_page); - $sql = "UPDATE GROUP_PAGE SET PAGE=? WHERE ID = ?"; - $result = $db->execute($sql, [$parsed_page, $page_id]); + $sql = "UPDATE GROUP_PAGE SET PAGE=?, LAST_MODIFIED=? WHERE ID = ?"; + $result = $db->execute($sql, [$parsed_page, $pubdate, $page_id]); } else { $discuss_thread = $this->addGroupItem(0, $group_id, $user_id, $thread_title, $thread_description . " " . date("r", $pubdate), C\WIKI_GROUP_ITEM); - $sql = "INSERT INTO GROUP_PAGE (DISCUSS_THREAD, GROUP_ID, - TITLE, PAGE, LOCALE_TAG) VALUES (?, ?, ?, ?, ?)"; + $sql = "INSERT INTO GROUP_PAGE (DISCUSS_THREAD, GROUP_ID, TITLE, + PAGE, LOCALE_TAG, LAST_MODIFIED) VALUES (?, ?, ?, ?, ?, ?)"; $result = $db->execute($sql, [$discuss_thread, $group_id, - $page_name, $parsed_page, $locale_tag]); + $page_name, $parsed_page, $locale_tag, $pubdate]); $page_id = $db->insertID("GROUP_PAGE"); ImpressionModel::initWithDb($user_id, $page_id, C\WIKI_IMPRESSION, $db); @@ -1728,8 +1728,8 @@ class GroupModel extends Model implements MediaConstants AND GP.TITLE = ? AND GP.LOCALE_TAG = ? AND HP.PAGE_ID = GP.ID ORDER BY HP.PUBDATE DESC " . $db->limitOffset(0, 1); } else { - $sql = "SELECT ID, PAGE, DISCUSS_THREAD FROM GROUP_PAGE - WHERE GROUP_ID = ? AND TITLE=? AND LOCALE_TAG = ?"; + $sql = "SELECT ID, PAGE, DISCUSS_THREAD, LAST_MODIFIED FROM + GROUP_PAGE WHERE GROUP_ID = ? AND TITLE=? AND LOCALE_TAG = ?"; } $result = $db->execute($sql, [$group_id, $name, $locale_tag]); if (!$result) { @@ -1742,8 +1742,8 @@ class GroupModel extends Model implements MediaConstants return $row; } /** - * Returns the group_id, language, and page name of a wiki page - * corresponding to a page discussion thread with id $page_thread_id + * Returns the group_id, language, page name, last modified date of a wiki + * pagecorresponding to a page discussion thread with id $page_thread_id * @param int $page_thread_id the id of a wiki page discussion thread * to look up page info for * @return array (group_id, language, and page name) of that wiki page @@ -1751,8 +1751,8 @@ class GroupModel extends Model implements MediaConstants public function getPageInfoByThread($page_thread_id) { $db = $this->db; - $sql = "SELECT GROUP_ID, LOCALE_TAG, TITLE AS PAGE_NAME FROM GROUP_PAGE - WHERE DISCUSS_THREAD = ?"; + $sql = "SELECT GROUP_ID, LOCALE_TAG, TITLE AS PAGE_NAME, + LAST_MODIFIED FROM GROUP_PAGE WHERE DISCUSS_THREAD = ?"; $result = $db->execute($sql, [$page_thread_id]); if (!$result) { return false; @@ -1772,8 +1772,8 @@ class GroupModel extends Model implements MediaConstants public function getPageInfoByPageId($page_id) { $db = $this->db; - $sql = "SELECT GROUP_ID, LOCALE_TAG, TITLE AS PAGE_NAME, DISCUSS_THREAD - AS DISCUSS_THREAD FROM GROUP_PAGE WHERE ID = ?"; + $sql = "SELECT GROUP_ID, LOCALE_TAG, TITLE AS PAGE_NAME, + DISCUSS_THREAD, LAST_MODIFIED FROM GROUP_PAGE WHERE ID = ?"; $result = $db->execute($sql, [$page_id]); if (!$result) { return false; @@ -4159,6 +4159,8 @@ EOD; * @param int $group_id of group want list of wiki pages for * @param string $locale_tag language want wiki page list for * @param string $filter string we want to filter wiki page title by + * @param string $sort one of name_asc, name_desc, date_asc, date_desc + * specifying how the page list should be sorted * @param string $limit first row we want from the result set * @param string $num number of rows we want starting from the first row * in the result set @@ -4167,10 +4169,17 @@ EOD; * $pages is an array each of whose elements is an array corresponding * to one TITLE and the first 100 chars out of a wiki page. */ - public function getPageList($group_id, $locale_tag, $filter, $limit, $num) + public function getPageList($group_id, $locale_tag, $filter, $sort, $limit, + $num) { $db = $this->db; $filter_parts = preg_split("/\s+/", $filter); + $sort_map = [ "name_asc" => "LOWER(TITLE) ASC", + "name_desc" => "LOWER(TITLE) DESC", + "modified_asc" => "LAST_MODIFIED ASC", + "modified_desc" => "LAST_MODIFIED DESC", + ]; + $sort_dir = $sort_map[$sort] ?? "LOWER(TITLE) ASC"; $like = ""; $params = [$group_id, $locale_tag]; foreach ($filter_parts as $part) { @@ -4189,10 +4198,10 @@ EOD; $total = (isset($row) && $row) ? $row["TOTAL"] : 0; $pages = []; if ($total > 0) { - $sql = "SELECT TITLE, PAGE AS DESCRIPTION + $sql = "SELECT TITLE, PAGE AS DESCRIPTION, LAST_MODIFIED FROM GROUP_PAGE WHERE GROUP_ID = ? AND LOCALE_TAG= ? AND LENGTH(PAGE) > 0 - $like ORDER BY LOWER(TITLE) ASC ". + $like ORDER BY $sort_dir ". $db->limitOffset($limit, $num); $result = $db->execute($sql, $params); $i = 0; diff --git a/src/models/ProfileModel.php b/src/models/ProfileModel.php index 0e507a22b..44a1eed05 100755 --- a/src/models/ProfileModel.php +++ b/src/models/ProfileModel.php @@ -205,7 +205,8 @@ class ProfileModel extends Model "GROUP_PAGE" => "CREATE TABLE GROUP_PAGE ( ID $serial PRIMARY KEY $auto_increment, GROUP_ID $integer, DISCUSS_THREAD $integer, TITLE VARCHAR(" . C\TITLE_LEN . "), - PAGE $page_type, LOCALE_TAG VARCHAR(" . C\NAME_LEN . "))", + PAGE $page_type, LOCALE_TAG VARCHAR(" . C\NAME_LEN . "), + LAST_MODIFIED NUMERIC(" . C\TIMESTAMP_LEN . "))", "GP_ID_INDEX" => "CREATE INDEX GP_ID_INDEX ON GROUP_PAGE (GROUP_ID, TITLE, LOCALE_TAG)", "GROUP_PAGE_HISTORY" => "CREATE TABLE GROUP_PAGE_HISTORY( @@ -390,16 +391,6 @@ class ProfileModel extends Model ACCESS_COUNT $integer, PRIMARY KEY(ADDRESS, PAGE_NAME))", "VERSION" => "CREATE TABLE VERSION(ID $integer PRIMARY KEY)", - "USER_TERM_WEIGHTS_HASH2VEC" => "CREATE TABLE - USER_TERM_WEIGHTS_HASH2VEC(TERM_ID $integer, USER_ID $integer, - WEIGHT FLOAT, PRIMARY KEY(TERM_ID, USER_ID))", - "USER_ITEM_SIMILARITY_HASH2VEC" => "CREATE TABLE - USER_ITEM_SIMILARITY_HASH2VEC(USER_ID $integer, THREAD_ID - $integer, SIMILARITY FLOAT, GROUP_MEMBER $integer, - PRIMARY KEY(USER_ID, THREAD_ID))", - "HASH2VEC_TERM_SIMILARITY" => "CREATE TABLE HASH2VEC_TERM_SIMILARITY - (TERM1 $integer, TERM2 $integer, SCORE FLOAT, - PRIMARY KEY(TERM1, TERM2))", ]; } /** diff --git a/src/views/elements/WikiElement.php b/src/views/elements/WikiElement.php index e77c62a5c..0126f2632 100644 --- a/src/views/elements/WikiElement.php +++ b/src/views/elements/WikiElement.php @@ -1617,6 +1617,13 @@ class WikiElement extends Element implements CrawlConstants <input type="hidden" name="arg" value="pages" /> <input type="hidden" name="group_id" value="<?= $data['GROUP']['GROUP_ID'] ?>" /> + <?php + $this->view->helper("options")->renderLinkDropDown( + "page-sort-fields", $data['sort_fields'] ?? "", + $data['CURRENT_SORT']?? "", "$paging_query&sort=", + false, "", + "<span class='hover-lightgray' role='img' aria-label='" . + tl('wiki_element_sort_order') . "'>⇅</span>"); ?> <div class="search-filter-container"> <input type="search" name="filter" class="extra-wide-field" maxlength="<?= C\SHORT_TITLE_LEN ?>" @@ -1636,6 +1643,7 @@ class WikiElement extends Element implements CrawlConstants <div> </div> <?php if ($data['PAGES'] != []) { + $time = time(); foreach ($data['PAGES'] as $page) { if ($page['TYPE'] == 'page_alias' && isset($page['ALIAS'])) { $page["DESCRIPTION"] = tl('wiki_element_redirect_to'). @@ -1671,7 +1679,10 @@ class WikiElement extends Element implements CrawlConstants "small-margin no-padding", "", false, $tab_target); } ?></br /> - <?= $page["DESCRIPTION"] ?> + <?= $page["DESCRIPTION"] ?><br /> + <span class="float-opposite gray"><?= + $this->view->helper("feeds")->getPubdateString( + $time, $page['LAST_MODIFIED']); ?></span> </div> <?php }?>