Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 61
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
CategoriesStorageManager
0.00% covered (danger)
0.00%
0 / 61
0.00% covered (danger)
0.00%
0 / 4
110
0.00% covered (danger)
0.00%
0 / 1
 update
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 create
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
20
 save
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 exists
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Storage manager for source and target categories in cx_corpora table. cx_corpora was initially
4 * used to store pairs of source and target (translated) sections, but extended to store pairs
5 * of source and target category lists, which utilize special section ID of 'CX_CATEGORY_METADATA'.
6 *
7 * @copyright See AUTHORS.txt
8 * @license GPL-2.0-or-later
9 */
10
11namespace ContentTranslation;
12
13use MediaWiki\MediaWikiServices;
14use Wikimedia\Rdbms\IDatabase;
15
16class CategoriesStorageManager {
17    private const TYPE_SOURCE = 'source';
18    private const TYPE_USER = 'user';
19
20    private static $CATEGORIES_SECTION = [
21        'cxc_section_id' => 'CX_CATEGORY_METADATA'
22    ];
23
24    /**
25     * Update source or target categories. Since API doesn't require source nor target categories
26     * while performing saving of a draft, if $categories isn't provided, do nothing.
27     *
28     * @param IDatabase $db
29     * @param int $translationId
30     * @param string $categories
31     * @param string $origin TYPE_SOURCE or TYPE_USER. Defaults to TYPE_USER
32     */
33    private static function update(
34        IDatabase $db, $translationId, $categories, $origin = self::TYPE_USER
35    ) {
36        if ( !$categories ) {
37            return;
38        }
39
40        $values = [
41            'cxc_timestamp' => $db->timestamp(),
42            'cxc_content' => $categories
43        ];
44        $conditions = [
45            'cxc_translation_id' => $translationId,
46            'cxc_origin' => $origin
47        ] + self::$CATEGORIES_SECTION;
48
49        $db->newUpdateQueryBuilder()
50            ->update( 'cx_corpora' )
51            ->set( $values )
52            ->where( $conditions )
53            ->caller( __METHOD__ )
54            ->execute();
55    }
56
57    /**
58     * Insert source and target category list for the given translation identifier.
59     *
60     * @param IDatabase $db
61     * @param int $translationId
62     * @param string $sourceCategories
63     * @param string $targetCategories
64     */
65    private static function create(
66        IDatabase $db, $translationId, $sourceCategories, $targetCategories
67    ) {
68        $values = [];
69        $commonValues = [
70            'cxc_translation_id' => $translationId,
71            'cxc_timestamp' => $db->timestamp()
72        ] + self::$CATEGORIES_SECTION;
73
74        if ( $targetCategories ) {
75            $values[] = [
76                'cxc_origin' => self::TYPE_USER,
77                'cxc_content' => $targetCategories
78            ] + $commonValues;
79        }
80
81        if ( $sourceCategories ) {
82            $values[] = [
83                'cxc_origin' => self::TYPE_SOURCE,
84                'cxc_content' => $sourceCategories
85            ] + $commonValues;
86        }
87
88        if ( $values !== [] ) {
89            $db->newInsertQueryBuilder()
90                ->insertInto( 'cx_corpora' )
91                ->rows( $values )
92                ->caller( __METHOD__ )
93                ->execute();
94        }
95    }
96
97    /**
98     * Save the source and target category list.
99     * If the records exists, update them, otherwise create.
100     *
101     * @param int $translationId
102     * @param string $sourceCategories
103     * @param string $targetCategories
104     * @param bool $newTranslation Whether these are for a brand new Translation record
105     */
106    public static function save(
107        $translationId, $sourceCategories, $targetCategories, $newTranslation
108    ) {
109        $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' );
110        $db = $lb->getConnection( DB_PRIMARY );
111
112        $db->doAtomicSection(
113            __METHOD__,
114            function ( IDatabase $db ) use (
115                $translationId, $sourceCategories, $targetCategories, $newTranslation
116            ) {
117                if ( $newTranslation ) {
118                    $existing = false;
119                } else {
120                    $existing = self::exists( $translationId );
121                }
122
123                if ( $existing ) {
124                    self::update( $db, $translationId, $sourceCategories, self::TYPE_SOURCE );
125                    self::update( $db, $translationId, $targetCategories );
126                } else {
127                    self::create( $db, $translationId, $sourceCategories, $targetCategories );
128                }
129            }
130        );
131    }
132
133    /**
134     * Find if there are records about source and target categories.
135     *
136     * @param int $translationId
137     * @return bool
138     */
139    private static function exists( $translationId ) {
140        $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' );
141        $db = $lb->getConnection( DB_PRIMARY );
142
143        $conditions = [ 'cxc_translation_id' => $translationId ] + self::$CATEGORIES_SECTION;
144
145        $result = $db->select(
146            'cx_corpora', 'cxc_content', $conditions, __METHOD__, [ 'FOR UPDATE' ]
147        );
148
149        return $result->numRows() > 0;
150    }
151}