source: branches/version-2_5-dev/data/class/SC_Product.php @ 18882

Revision 18882, 25.4 KB checked in by nanasess, 12 years ago (diff)

#823 商品種別によってカートを分ける

  • 「実商品・ダウンロード」を「商品種別」に変更
Line 
1<?php
2/*
3 * This file is part of EC-CUBE
4 *
5 * Copyright(c) 2000-2010 LOCKON CO.,LTD. All Rights Reserved.
6 *
7 * http://www.lockon.co.jp/
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22 */
23
24/**
25 * 商品を扱うサービスクラス.
26 *
27 * @author LOCKON CO.,LTD.
28 * @author Kentaro Ohkouchi
29 * @version $Id$
30 */
31class SC_Product {
32
33    /** 規格名一覧 */
34    var $arrClassName;
35    /** 規格分類名一覧 */
36    var $arrClassCatName;
37    var $classCategories = array();
38    var $stock_find;
39    /** 規格1クラス名 */
40    var $className1 = '';
41    /** 規格2クラス名 */
42    var $className2 = '';
43    /** 規格1が設定されている */
44    var $classCat1_find;
45    /** 規格2が設定されている */
46    var $classCat2_find;
47    var $classCats1;
48
49    /**
50     * SC_Queryインスタンスに設定された検索条件をもとに商品IDの配列を取得する.
51     *
52     * 検索条件は, SC_Query::getWhere() 関数で設定しておく必要があります.
53     *
54     * @param SC_Query $objQuery SC_Query インスタンス
55     * @param array $arrVal 検索パラメータの配列
56     * @return array 商品IDの配列
57     */
58    function findProductIds(&$objQuery, $arrVal = array()) {
59        $table = <<< __EOS__
60                 dtb_products AS alldtl
61            JOIN dtb_product_categories AS T2
62              ON alldtl.product_id = T2.product_id
63            JOIN dtb_category
64              ON T2.category_id = dtb_category.category_id
65__EOS__;
66        // SC_Query::getCol() ではパフォーマンスが出ない
67        $results = $objQuery->select('alldtl.product_id', $table, "", $arrVal,
68                                     MDB2_FETCHMODE_ORDERED);
69        foreach ($results as $val) {
70            $resultValues[] = $val[0];
71        }
72        return array_unique($resultValues);
73    }
74
75    /**
76     * SC_Queryインスタンスに設定された検索条件をもとに商品一覧の配列を取得する.
77     *
78     * 主に SC_Product::findProductIds() で取得した商品IDを検索条件にし,
79     * SC_Query::setOrder() や SC_Query::setLimitOffset() を設定して, 商品一覧
80     * の配列を取得する.
81     *
82     * @param SC_Query $objQuery SC_Query インスタンス
83     * @param array $arrVal 検索パラメータ(ソート条件)の配列
84     * @return array 商品一覧の配列
85     */
86    function lists(&$objQuery, $arrVal = array()) {
87        $col = <<< __EOS__
88             product_id
89            ,product_code_min
90            ,product_code_max
91            ,name
92            ,comment1
93            ,comment2
94            ,comment3
95            ,main_list_comment
96            ,main_image
97            ,main_list_image
98            ,price01_min
99            ,price01_max
100            ,price02_min
101            ,price02_max
102            ,stock_min
103            ,stock_max
104            ,stock_unlimited_min
105            ,stock_unlimited_max
106            ,deliv_date_id
107            ,status
108            ,del_flg
109            ,update_date
110__EOS__;
111        return $objQuery->select($col, $this->alldtlSQL($objQuery->where),
112                                 "", $arrVal);
113    }
114
115    /**
116     * 商品詳細を取得する.
117     *
118     * @param integer $productId 商品ID
119     * @return array 商品詳細情報の配列
120     */
121    function getDetail($productId) {
122        $objQuery =& SC_Query::getSingletonInstance();
123        $result = $objQuery->select("*", $this->alldtlSQL("product_id = ?"),
124                                    "product_id = ?",
125                                    array($productId, $productId));
126        return $result[0];
127    }
128
129    /**
130     * 商品詳細情報と商品規格を取得する.
131     *
132     * @param integer $productClassId 商品規格ID
133     * @return array 商品詳細情報と商品規格の配列
134     */
135    function getDetailAndProductsClass($productClassId) {
136        $result = $this->getProductsClass($productClassId);
137        $result = array_merge($result, $this->getDetail($result['product_id']));
138        return $result;
139    }
140
141    /**
142     * 商品IDに紐づく商品規格を自分自身に設定する.
143     *
144     * 引数の商品IDの配列に紐づく商品規格を取得し, 自分自身のフィールドに
145     * 設定する.
146     *
147     * @param array $arrProductId 商品ID の配列
148     * @return void
149     */
150    function setProductsClassByProductIds($arrProductId) {
151
152        foreach ($arrProductId as $productId) {
153            $rows[$productId] = $this->getProductsClassFullByProductId($productId);
154        }
155
156        $arrProductsClass = array();
157        foreach ($rows as $productId => $arrProductClass) {
158            $classCats1 = array();
159            $classCats1[''] = '選択してください';
160
161            // 規格1クラス名
162            $this->className1[$productId] =
163                isset($arrProductClass[0]['class_name1'])
164                ? $arrProductClass[0]['class_name1']
165                : '';
166
167            // 規格2クラス名
168            $this->className2[$productId] =
169                isset($arrProductClass[0]['class_name2'])
170                ? $arrProductClass[0]['class_name2']
171                : '';
172
173            // 規格1が設定されている
174            $this->classCat1_find[$productId] = (!SC_Utils_Ex::isBlank($arrProductClass[0]['classcategory_id1']));
175            // 規格2が設定されている
176            $this->classCat2_find[$productId] = (!SC_Utils_Ex::isBlank($arrProductClass[0]['classcategory_id2']));
177
178            $this->stock_find[$productId] = false;
179            $classCategories = array();
180            $classCategories['']['']['name'] = '選択してください';
181            $classCategories['']['']['product_class_id'] = $arrProductClass[0]['product_class_id'];
182            // 商品種別
183            $classCategories['']['']['product_type'] = $arrProductClass[0]['product_type_id'];
184            $this->product_class_id[$productId] = $arrProductClass[0]['product_class_id'];
185            // 商品種別
186            $this->product_type[$productId] = $arrProductClass[0]['product_type_id'];
187            foreach ($arrProductClass as $productsClass) {
188                $productsClass1 = $productsClass['classcategory_id1'];
189                $productsClass2 = $productsClass['classcategory_id2'];
190                $classCategories[$productsClass1]['']['name'] = '選択してください';
191                // 在庫
192                $stock_find_class = ($productsClass['stock_unlimited'] || $productsClass['stock'] > 0);
193
194                $classCategories[$productsClass1][$productsClass2]['name'] = $productsClass['classcategory_name2'] . ($stock_find_class ? '' : ' (品切れ中)');
195
196                $classCategories[$productsClass1][$productsClass2]['stock_find'] = $stock_find_class;
197
198                if ($stock_find_class) {
199                    $this->stock_find[$productId] = true;
200                }
201
202                if (!in_array($classcat_id1, $classCats1)) {
203                    $classCats1[$productsClass1] = $productsClass['classcategory_name1']
204                        . ($productsClass2 == 0 && !$stock_find_class ? ' (品切れ中)' : '');
205                }
206
207                // 価格
208                $classCategories[$productsClass1][$productsClass2]['price01']
209                    = strlen($productsClass['price01'])
210                    ? number_format(SC_Helper_DB_Ex::sfPreTax($productsClass['price01']))
211                    : '';
212
213                $classCategories[$productsClass1][$productsClass2]['price02']
214                    = strlen($productsClass['price02'])
215                    ? number_format(SC_Helper_DB_Ex::sfPreTax($productsClass['price02']))
216                    : '';
217
218                // ポイント
219                // XXX sfPrePoint() の第4パラメータは、処理にバグがあるため現状省略している。(http://xoops.ec-cube.net/modules/newbb/viewtopic.php?topic_id=3540&forum=1&post_id=13853#forumpost13853)
220                $classCategories[$productsClass1][$productsClass2]['point']
221                    = SC_Utils_Ex::sfPrePoint($productsClass['price02'], $productsClass['point_rate']);
222
223                // 商品コード
224                $classCategories[$productsClass1][$productsClass2]['product_code'] = $productsClass['product_code'];
225                // 商品規格ID
226                $classCategories[$productsClass1][$productsClass2]['product_class_id'] = $productsClass['product_class_id'];
227                // 商品種別
228                $classCategories[$productsClass1][$productsClass2]['product_type'] = $productsClass['product_type_id'];
229            }
230
231            $this->classCategories[$productId] = $classCategories;
232
233            // 規格1
234            $this->classCats1[$productId] = $classCats1;
235        }
236    }
237
238    /**
239     * SC_Query インスタンスに設定された検索条件を使用して商品規格を取得する.
240     *
241     * @param SC_Query $objQuery SC_Queryインスタンス
242     * @param array $params 検索パラメータの配列
243     * @return array 商品規格の配列
244     */
245    function getProductsClassByQuery(&$objQuery, $params) {
246        // 末端の規格を取得
247        $col = <<< __EOS__
248            T1.product_id,
249            T1.stock,
250            T1.stock_unlimited,
251            T1.price01,
252            T1.price02,
253            T1.point_rate,
254            T1.product_code,
255            T1.product_class_id,
256            T1.del_flg,
257            T1.product_type_id,
258            T1.down_filename,
259            T1.down_realfilename,
260            T2.class_combination_id,
261            T2.parent_class_combination_id,
262            T2.classcategory_id,
263            T2.level,
264            T3.name AS classcategory_name,
265            T4.name AS class_name,
266            T4.class_id
267__EOS__;
268        $table = <<< __EOS__
269                      dtb_products_class T1
270            LEFT JOIN dtb_class_combination T2
271                   ON T1.class_combination_id = T2.class_combination_id
272            LEFT JOIN dtb_classcategory T3
273                   ON T2.classcategory_id = T3.classcategory_id
274            LEFT JOIN dtb_class T4
275                   ON T3.class_id = T4.class_id
276__EOS__;
277        $arrRet = $objQuery->select($col, $table, "", $params);
278        $levels = array();
279        $parents = array();
280        foreach ($arrRet as $rows) {
281            $levels[] = $rows['level'];
282            $parents[] = $rows['parent_class_combination_id'];
283        }
284        $level = max($levels);
285        $parentsClass = array();
286        // 階層分の親を取得
287        for ($i = 0; $i < $level -1; $i++) {
288            $objQuery =& SC_Query::getSingletonInstance();
289            $objQuery->setWhere('T1.class_combination_id IN (' . implode(', ', array_pad(array(), count($parents), '?')) . ')');
290
291            $col = <<< __EOS__
292                T1.class_combination_id,
293                T1.classcategory_id,
294                T1.parent_class_combination_id,
295                T1.level,
296                T2.name AS classcategory_name,
297                T3.name AS class_name,
298                T3.class_id
299__EOS__;
300            $table = <<< __EOS__
301                          dtb_class_combination T1
302                LEFT JOIN dtb_classcategory T2
303                       ON T1.classcategory_id = T2.classcategory_id
304                LEFT JOIN dtb_class T3
305                       ON T2.class_id = T3.class_id
306__EOS__;
307
308            $arrParents = $objQuery->select($col, $table, "", $parents);
309
310            unset($parents);
311            foreach ($arrParents as $rows) {
312                $parents[] = $rows['parent_class_combination_id'];
313
314                foreach ($arrRet as $child) {
315                    if ($child['parent_class_combination_id']
316                        == $rows['class_combination_id']) {
317                        $rows['product_id'] = $child['product_id'];
318                    }
319                }
320                $tmpParents[] = $rows;
321            }
322            $parentsClass = array_merge($parentsClass, $tmpParents);
323        }
324
325        // 末端から枝を作成
326        $tmpClass = array_merge($arrRet, $parentsClass);
327
328        foreach ($tmpClass as $val) {
329            $val['class_id' . $val['level']] = $val['class_id'];
330            $val['class_name' . $val['level']] = $val['class_name'];
331            $val['classcategory_name' . $val['level']] = $val['classcategory_name'];
332            $val['classcategory_id' . $val['level']] = $val['classcategory_id'];
333            $arrProductsClass[] = $val;
334        }
335
336        return $arrProductsClass;
337    }
338
339    /**
340     * 商品規格IDから商品規格を取得する.
341     */
342    function getProductsClass($productClassId) {
343        $objQuery =& SC_Query::getSingletonInstance();
344        $objQuery->setWhere('product_class_id = ?');
345        $objQuery->setOrder("T2.level DESC");
346        $results = $this->getProductsClassByQuery($objQuery, $productClassId);
347        $productsClass = $this->getProductsClassFull($results);
348        return $productsClass[0];
349    }
350
351    /**
352     * 複数の商品IDに紐づいた, 商品規格を取得する.
353     *
354     * @param array $productIds 商品IDの配列
355     * @return array 商品規格の配列
356     */
357    function getProductsClassByProductIds($productIds = array()) {
358        if (empty($productIds)) {
359            return array();
360        }
361        $objQuery =& SC_Query::getSingletonInstance();
362        $objQuery->setWhere('product_id IN (' . implode(', ', array_pad(array(), count($productIds), '?')) . ')');
363        $objQuery->setOrder("T2.level DESC");
364        return $this->getProductsClassByQuery($objQuery, $productIds);
365    }
366
367    /**
368     * 商品IDに紐づいた, 商品規格を階層ごとに取得する.
369     *
370     * @param array $productId 商品ID
371     * @return array 階層ごとの商品規格の配列
372     */
373    function getProductsClassLevelByProductId($productId) {
374        $results = $this->getProductsClassByProductIds(array($productId));
375        return $this->getProductsClassLevel($results);
376    }
377
378    /**
379     * 商品IDに紐づいた, 商品規格をすべての組み合わせごとに取得する.
380     *
381     * @param array $productId 商品ID
382     * @return array すべての組み合わせの商品規格の配列
383     */
384    function getProductsClassFullByProductId($productId) {
385        $results = $this->getProductsClassByProductIds(array($productId));
386        return $this->getProductsClassFull($results);
387    }
388
389    /**
390     * 商品規格の配列から, 商品規格を階層ごとに取得する.
391     *
392     * @access private
393     * @param array $productsClassResults 商品規格の結果の配列
394     * @return array 階層ごとの商品規格の配列
395     */
396    function getProductsClassLevel($productsClassResults) {
397        foreach ($productsClassResults as $row) {
398            $productsClassLevel["level" . $row['level']][] = $row;
399        }
400        return $productsClassLevel;
401    }
402
403    /**
404     * 商品規格の配列から, 商品規格のすべての組み合わせを取得する.
405     *
406     * @access private
407     * @param array $productsClassResults 商品規格の結果の配列
408     * @ array 階層ごとの商品規格の配列
409     */
410    function getProductsClassFull($productsClassResults) {
411        $results = $this->getProductsClassLevel($productsClassResults);
412        $productsClass = array();
413        if (SC_Utils_Ex::isBlank($results["level1"])
414            && SC_Utils_Ex::isBlank($results["level2"])) {
415            return $results["level"];
416        }
417
418        foreach ($results["level1"] as $level1) {
419            foreach ($results["level2"] as $level2) {
420                if ($level2['parent_class_combination_id'] == $level1['class_combination_id']) {
421                    $level1 = array_merge($level1, $level2);
422                }
423            }
424            $productsClass[] = $level1;
425        }
426        return $productsClass;
427    }
428
429    /**
430     * 商品IDをキーにした, 商品ステータスIDの配列を取得する.
431     *
432     * @param array 商品ID の配列
433     * @return array 商品IDをキーにした商品ステータスIDの配列
434     */
435    function getProductStatus($productIds) {
436        $objQuery =& SC_Query::getSingletonInstance();
437        $productStatus = $objQuery->select("product_id, product_status_id",
438                                           "dtb_product_status",
439                                           'del_flg = 0 AND product_id IN (' . implode(', ', array_pad(array(), count($productIds), '?')) . ')', $productIds);
440        $results = array();
441        foreach ($productStatus as $status) {
442            $results[$status['product_id']][] = $status['product_status_id'];
443        }
444        return $results;
445    }
446
447    /**
448     * 商品ステータスを設定する.
449     *
450     * TODO 現在は DELETE/INSERT だが, UPDATE を検討する.
451     *
452     * @param integer $productId 商品ID
453     * @param array $productStatusIds ON にする商品ステータスIDの配列
454     */
455    function setProductStatus($productId, $productStatusIds) {
456
457        $val['product_id'] = $productId;
458        $val['creator_id'] = $_SESSION['member_id'];
459        $val['create_date'] = 'Now()';
460        $val['update_date'] = 'Now()';
461        $val['del_flg'] = '0';
462
463        $objQuery =& SC_Query::getSingletonInstance();
464        $objQuery->delete('dtb_product_status', 'product_id = ?', array($productId));
465        foreach ($productStatusIds as $productStatusId) {
466            $val['product_status_id'] = $productStatusId;
467            $objQuery->insert('dtb_product_status', $val);
468        }
469    }
470
471    /**
472     * 商品詳細の結果から, 購入制限数を取得する.
473     *
474     * getDetailAndProductsClass() の結果から, 購入制限数を取得する.
475     *
476     * @param array $p 商品詳細の検索結果の配列
477     * @return integer 商品詳細の結果から求めた購入制限数.
478     * @see getDetailAndProductsClass()
479     */
480    function getBuyLimit($p) {
481        $limit = null;
482        if ($p['stock_unlimited'] != '1' && is_numeric($p['sale_limit'])) {
483            $limit = min($p['sale_limit'], $p['stock']);
484        } elseif (is_numeric($p['sale_limit'])) {
485            $limit = $p['sale_limit'];
486        } elseif ($p['stock_unlimited'] != '1') {
487            $limit = $p['stock'];
488        }
489        return $limit;
490    }
491
492    /**
493     * 在庫を減少させる.
494     *
495     * 指定の在庫数まで, 在庫を減少させる.
496     * 減少させた結果, 在庫数が 0 未満になった場合, 引数 $quantity が 0 の場合は,
497     * 在庫の減少を中止し, false を返す.
498     * 在庫の減少に成功した場合は true を返す.
499     *
500     * @param integer $productClassId 商品規格ID
501     * @param integer $quantity 減少させる在庫数
502     * @return boolean 在庫の減少に成功した場合 true; 失敗した場合 false
503     */
504    function reduceStock($productClassId, $quantity) {
505
506        $productsClass = $this->getDetailAndProductsClass($productClassId);
507        if (($productsClass['stock_unlimited'] != '1'
508             && $productsClass['stock'] < $quantity)
509            || $quantity == 0) {
510            return false;
511        }
512
513        $objQuery =& SC_Query::getSingletonInstance();
514        $objQuery->update('dtb_products_class', array(),
515                          "product_class_id = ?", array($productClassId),
516                          array('stock' => 'stock - ?'), array($quantity));
517        // TODO エラーハンドリング
518        return true;
519    }
520
521    /**
522     * 商品詳細の SQL を取得する.
523     *
524     * @param string $where 商品詳細の WHERE 句
525     * @return string 商品詳細の SQL
526     */
527    function alldtlSQL($where = "") {
528        $whereCause = "";
529        if (!SC_Utils_Ex::isBlank($where)) {
530            $whereCause = " WHERE " . $where;
531        }
532        /*
533         * point_rate は商品規格(dtb_products_class)ごとに保持しているが,
534         * 商品(dtb_products)ごとの設定なので MAX のみを取得する.
535         */
536        $sql = <<< __EOS__
537            (
538             SELECT dtb_products.product_id,
539                    dtb_products.name,
540                    dtb_products.maker_id,
541                    dtb_products.rank,
542                    dtb_products.status,
543                    dtb_products.comment1,
544                    dtb_products.comment2,
545                    dtb_products.comment3,
546                    dtb_products.comment4,
547                    dtb_products.comment5,
548                    dtb_products.comment6,
549                    dtb_products.note,
550                    dtb_products.main_list_comment,
551                    dtb_products.main_list_image,
552                    dtb_products.main_comment,
553                    dtb_products.main_image,
554                    dtb_products.main_large_image,
555                    dtb_products.sub_title1,
556                    dtb_products.sub_comment1,
557                    dtb_products.sub_image1,
558                    dtb_products.sub_large_image1,
559                    dtb_products.sub_title2,
560                    dtb_products.sub_comment2,
561                    dtb_products.sub_image2,
562                    dtb_products.sub_large_image2,
563                    dtb_products.sub_title3,
564                    dtb_products.sub_comment3,
565                    dtb_products.sub_image3,
566                    dtb_products.sub_large_image3,
567                    dtb_products.sub_title4,
568                    dtb_products.sub_comment4,
569                    dtb_products.sub_image4,
570                    dtb_products.sub_large_image4,
571                    dtb_products.sub_title5,
572                    dtb_products.sub_comment5,
573                    dtb_products.sub_image5,
574                    dtb_products.sub_large_image5,
575                    dtb_products.sub_title6,
576                    dtb_products.sub_comment6,
577                    dtb_products.sub_image6,
578                    dtb_products.sub_large_image6,
579                    dtb_products.del_flg,
580                    dtb_products.creator_id,
581                    dtb_products.create_date,
582                    dtb_products.update_date,
583                    dtb_products.deliv_date_id,
584                    T4.product_code_min,
585                    T4.product_code_max,
586                    T4.price01_min,
587                    T4.price01_max,
588                    T4.price02_min,
589                    T4.price02_max,
590                    T4.stock_min,
591                    T4.stock_max,
592                    T4.stock_unlimited_min,
593                    T4.stock_unlimited_max,
594                    T4.point_rate,
595                    T4.class_count
596               FROM dtb_products
597               JOIN (
598                       SELECT product_id,
599                              MIN(product_code) AS product_code_min,
600                              MAX(product_code) AS product_code_max,
601                              MIN(price01) AS price01_min,
602                              MAX(price01) AS price01_max,
603                              MIN(price02) AS price02_min,
604                              MAX(price02) AS price02_max,
605                              MIN(stock) AS stock_min,
606                              MAX(stock) AS stock_max,
607                              MIN(stock_unlimited) AS stock_unlimited_min,
608                              MAX(stock_unlimited) AS stock_unlimited_max,
609                              MAX(point_rate) AS point_rate,
610                              COUNT(*) as class_count
611                         FROM dtb_products_class
612                       $whereCause
613                     GROUP BY product_id
614                     ) AS T4
615                 ON dtb_products.product_id = T4.product_id
616        ) AS alldtl
617__EOS__;
618        return $sql;
619    }
620
621    /**
622     * 商品規格ID1、2に紐づいた,product_class_idを取得する.
623     *
624     * @param int $productId 商品ID
625     * @param int $classcategory_id1 商品規格ID1
626     * @param int $classcategory_id2 商品規格ID2
627     * @return string product_class_id
628     */
629    function getClasscategoryIdsByProductClassId($productId, $classcategory_id1, $classcategory_id2) {
630        $objQuery = new SC_Query();
631        $col = "T1.product_id AS product_id,T1.product_class_id AS product_class_id,T1.classcategory_id1 AS classcategory_id1,T1.classcategory_id2 AS classcategory_id2";
632        $table = <<< __EOS__
633            (SELECT
634                pc.product_code AS product_code,
635                pc.product_id AS product_id,
636                pc.product_class_id AS product_class_id,
637                pc.class_combination_id AS class_combination_id,
638                COALESCE(cc2.classcategory_id,0) AS classcategory_id1,
639                COALESCE(cc1.classcategory_id,0) AS classcategory_id2
640            FROM
641                dtb_products_class pc LEFT JOIN dtb_class_combination cc1 ON pc.class_combination_id = cc1.class_combination_id
642                LEFT JOIN dtb_class_combination cc2 ON cc1.parent_class_combination_id = cc2.class_combination_id) T1
643__EOS__;
644        $where = "T1.product_id = ? AND T1.classcategory_id1 = ? AND T1.classcategory_id2 = ?";
645        $arrRet = $objQuery->select($col, $table, $where,
646                                    array($productId, $classcategory_id1, $classcategory_id2));
647        return $arrRet[0]['product_class_id'];
648    }
649
650}
651?>
Note: See TracBrowser for help on using the repository browser.